@yichus/mcp 0.1.2 → 0.1.3

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 CHANGED
@@ -1,10 +1,10 @@
1
1
  # @yichus/mcp
2
2
 
3
- MCP server that exposes the [Yichus](https://yich.us) calculators and the
3
+ MCP server that exposes the [Yichus](https://yich.us) data apps and the
4
4
  in-browser Bosatsu playground to any agent that speaks the Model Context
5
5
  Protocol.
6
6
 
7
- It bundles the local WebMCP relay and adds calculator discovery, opening, and saving tools.
7
+ It bundles the local WebMCP relay and adds data app discovery, opening, and saving tools.
8
8
  A connected agent works with the same page the user can see: source edits,
9
9
  compiler diagnostics, results, and program diagrams.
10
10
 
@@ -18,13 +18,13 @@ launch it as a stdio server:
18
18
  "mcpServers": {
19
19
  "yichus": {
20
20
  "command": "npx",
21
- "args": ["-y", "@yichus/mcp@0.1.2"]
21
+ "args": ["-y", "@yichus/mcp@0.1.3"]
22
22
  }
23
23
  }
24
24
  }
25
25
  ```
26
26
 
27
- Version 0.1.0 has no calculator saving tool (`yichus_save_calculator`) and
27
+ Version 0.1.0 has no data app saving tool (`yichus_save_calculator`) and
28
28
  lacks later relay fixes.
29
29
 
30
30
  Open the desired page with `?webmcp=1`. Keep it open, call
@@ -41,7 +41,7 @@ A browser host with native WebMCP support can call page tools without this
41
41
  relay. See [connection instructions](https://yich.us/reference/agent.html).
42
42
 
43
43
  When an agent connects, the relay tells it what the open pages offer and
44
- that building the person's own app is the studio's job (`npx -y yichus@0.3.1
44
+ that building the person's own app is the studio's job (`npx -y yichus@0.3.4
45
45
  studio`, a separate MCP server in the project folder), not this relay's.
46
46
 
47
47
  ## Choose the tools for the page
@@ -50,11 +50,11 @@ studio`, a separate MCP server in the project folder), not this relay's.
50
50
  `yichus_example_guide`, `yichus_examples_list`, and `yichus_example_read`.
51
51
  Use `yichus_example_update` with exact file names and the read revision;
52
52
  `yichus_example_run` compiles/runs or checks the program. Inspect
53
- `yichus_example_feedback`. Once a calculator preview is ready,
53
+ `yichus_example_feedback`. Once a data app preview is ready,
54
54
  `yichus_example_preview` changes its visible controls or clicks Why.
55
55
  Pass the current source revision and preview id, so an old call cannot
56
56
  act on a replacement preview. Read-only demo drawers report that limit.
57
- - **Calculator editor**, `editor.html`: `yichus_playground_get_sources`,
57
+ - **Data app editor**, `editor.html`: `yichus_playground_get_sources`,
58
58
  `yichus_playground_update_sources`, `yichus_playground_compile`,
59
59
  `yichus_playground_get_output`, and `yichus_playground_reset`. Read sources
60
60
  first and pass their revision as `expectedRevision` when updating. Output
@@ -65,9 +65,9 @@ studio`, a separate MCP server in the project folder), not this relay's.
65
65
  and `api_why` update the visible diagrams/evaluator. The `yichus_app_*`
66
66
  tools open a generated app, exercise routes, inspect rows, and test
67
67
  configured crash/restart behavior. The page links their reference.
68
- - **Standalone calculator**: `yichus_list_inputs`, `yichus_set_inputs`,
68
+ - **Standalone data app**: `yichus_list_inputs`, `yichus_set_inputs`,
69
69
  `yichus_list_outputs`, `yichus_explain_output`. These operate the running
70
- calculator; use an editable source drawer or the editor to change code.
70
+ data app; use an editable source drawer or the editor to change code.
71
71
  An explanation is plain data: the formula in the page's labels, the same
72
72
  formula with current values, the inputs and outputs it reads, and the
73
73
  unlabeled steps in between.
@@ -81,7 +81,7 @@ studio`, a separate MCP server in the project folder), not this relay's.
81
81
 
82
82
  The static `yichus_list_calculators` tool lists curated demo URLs;
83
83
  `yichus_open_calculator` opens a chosen slug with the relay flag. The
84
- `playground` slug opens the standalone calculator editor. On a machine with
84
+ `playground` slug opens the standalone data app editor. On a machine with
85
85
  no browser opener (a container or headless box) it answers `opened: false`
86
86
  with the link for a person to open by hand.
87
87
 
@@ -92,7 +92,7 @@ with the link for a person to open by hand.
92
92
  If that revision is stale, read the user's newer edit before proceeding.
93
93
  3. Compile and fix diagnostics. For an API example, rerun the appropriate
94
94
  structural check and a behavior check.
95
- 4. Inspect feedback from the current result. Change a calculator input and
95
+ 4. Inspect feedback from the current result. Change a data app input and
96
96
  inspect Why. Report tested inputs, actual results, and remaining limits.
97
97
  5. Save the edited sources before leaving the page. A successful compilation
98
98
  and a few executions do not establish that every model assumption is sound.
@@ -103,10 +103,10 @@ The user accepts the ghost text with Tab or dismisses it with Escape.
103
103
  `yichus_autocomplete_list_editors`, `yichus_autocomplete_get_context`, and
104
104
  `yichus_autocomplete_dismiss` support inspection and cancellation.
105
105
 
106
- ## Save a browser-compiled calculator
106
+ ## Save a browser-compiled data app
107
107
 
108
108
  The [agent QuickStart](https://yich.us/start/quickstart.html) walks through a
109
- calculator change, compilation, Why feedback, and local execution.
109
+ data app change, compilation, Why feedback, and local execution.
110
110
 
111
111
  Use `webmcp_list_sources` to identify that page, then `webmcp_list_tools`
112
112
  to find its tools by their `sources`. Call the returned public `name`:
@@ -115,7 +115,7 @@ the plain name and later tabs' copies gain a tab-id suffix. The
115
115
  `webmcp_call_tool` arguments are `name` and `arguments`; it does not accept
116
116
  a `sourceId` parameter.
117
117
 
118
- After testing the calculator, call the relay's `yichus_save_calculator`
118
+ After testing the data app, call the relay's `yichus_save_calculator`
119
119
  directly with:
120
120
 
121
121
  ```json
@@ -140,7 +140,7 @@ Open the HTML or serve its directory with a static server. It starts at
140
140
  the configured defaults, and supporting scripts and styles still load
141
141
  from the exported site's URL, so internet access is required. The save
142
142
  tool does not execute the program. In other browser hosts, use the page's
143
- **Save calculator HTML** button or implement the paged
143
+ **Save data app HTML** button or implement the paged
144
144
  `yichus_example_export` contract (`nextOffset` and `expectedExportId`).
145
145
 
146
146
  ## CLI
@@ -168,7 +168,7 @@ For a self-hosted copy of the site, point both the relay's allowed origin
168
168
  and the URL builder at its server:
169
169
 
170
170
  ```bash
171
- npx -y @yichus/mcp@0.1.2 --base-url http://localhost:8000
171
+ npx -y @yichus/mcp@0.1.3 --base-url http://localhost:8000
172
172
  ```
173
173
 
174
174
  ## What this package does and does not do
package/dist/cli.js CHANGED
@@ -394,7 +394,7 @@ async function saveCalculator(rawArgs, invoke) {
394
394
 
395
395
  // src/server.ts
396
396
  var PACKAGE_NAME = "yichus-mcp";
397
- var PACKAGE_VERSION = "0.1.2";
397
+ var PACKAGE_VERSION = "0.1.3";
398
398
  function createYichusMcpServer(options = {}) {
399
399
  const baseUrl = normalizeBaseUrl(options.baseUrl ?? "https://yich.us");
400
400
  const bridgeOverrides = options.bridgeOverrides ?? {};
@@ -420,9 +420,9 @@ function createYichusMcpServer(options = {}) {
420
420
  answerRefusalsAsThePageWroteThem(server, bridge);
421
421
  return server;
422
422
  }
423
- var relayInstructions = (baseUrl) => `Yichus in the browser: the tools here come from ${new URL(baseUrl).host} pages open in the person's browser, such as a calculator, the playground or the workbench. yichus_list_calculators and yichus_open_calculator open one, webmcp_list_tools lists what the open tabs offer, and each page's tools say what they do. Use them to explore a page with the person and explain what it shows.
423
+ var relayInstructions = (baseUrl) => `Yichus in the browser: the tools here come from ${new URL(baseUrl).host} pages open in the person's browser, such as a data app, the playground or the workbench. yichus_list_calculators and yichus_open_calculator open one, webmcp_list_tools lists what the open tabs offer, and each page's tools say what they do. Use them to explore a page with the person and explain what it shows.
424
424
 
425
- To build the person's own app with them, use the Yichus studio instead. It is its own MCP server, run in their project folder (npx -y yichus@0.3.1 studio). It asks the person the product questions, checks the program after every change, and keeps a page open where you can point at what you mean while you talk.`;
425
+ To build the person's own app with them, use the Yichus studio instead. It is its own MCP server, run in their project folder (npx -y yichus@0.3.4 studio). It asks the person the product questions, checks the program after every change, and keeps a page open where you can point at what you mean while you talk.`;
426
426
  var TOOL_LIST_CHANGED_WINDOW_MS = 100;
427
427
  var isFunction = (field) => typeof field === "function";
428
428
  var StaticToolFields = z3.object({
@@ -461,7 +461,7 @@ function registerYichusTools(server, ctx) {
461
461
  });
462
462
  registerStatic(host, {
463
463
  name: "yichus_list_calculators",
464
- 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.",
464
+ description: "List the curated Yichus data app demos and the playground editor available on the configured Yichus origin. Returns a slug, title, description, and absolute URL for each.",
465
465
  annotations: { readOnlyHint: true, idempotentHint: true },
466
466
  input: z3.object({}),
467
467
  handler: async () => {
@@ -480,11 +480,11 @@ function registerYichusTools(server, ctx) {
480
480
  });
481
481
  registerStatic(host, {
482
482
  name: "yichus_open_calculator",
483
- 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.",
483
+ description: "Open a curated Yichus data app (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 data apps; yichus_playground_* for the playground). Use yichus_list_calculators to discover slugs.",
484
484
  annotations: { openWorldHint: true },
485
485
  input: z3.object({
486
486
  slug: z3.string().describe(
487
- 'Calculator slug from yichus_list_calculators (for example "loan", "investment", "tax", "playground").'
487
+ 'Data app slug from yichus_list_calculators (for example "loan", "investment", "tax", "playground").'
488
488
  )
489
489
  }),
490
490
  handler: async (args) => {
@@ -495,7 +495,7 @@ function registerYichusTools(server, ctx) {
495
495
  content: [
496
496
  {
497
497
  type: "text",
498
- text: `Unknown calculator slug "${args.slug}". Known slugs: ${known}.`
498
+ text: `Unknown data app slug "${args.slug}". Known slugs: ${known}.`
499
499
  }
500
500
  ],
501
501
  isError: true
@@ -581,7 +581,7 @@ function printHelp() {
581
581
 
582
582
  Yichus MCP server: bundles the WebMCP local relay with three static tools
583
583
  (yichus_list_calculators, yichus_open_calculator, yichus_save_calculator) so an MCP client
584
- can drive any deployed Yichus calculator end to end.
584
+ can drive any deployed Yichus data app end to end.
585
585
 
586
586
  Options:
587
587
  --base-url Yichus deployment to talk to (default: https://yich.us).
@@ -604,7 +604,7 @@ Add to your MCP client config:
604
604
  "mcpServers": {
605
605
  "yichus": {
606
606
  "command": "npx",
607
- "args": ["-y", "@yichus/mcp@0.1.2"]
607
+ "args": ["-y", "@yichus/mcp@0.1.3"]
608
608
  }
609
609
  }
610
610
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/server.ts","../src/calculators.ts","../src/calculators.json","../src/openUrl.ts","../src/relay.ts","../src/saveCalculator.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from 'node:util';\nimport { z } from 'zod';\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\nconst Port = z.coerce.number().int().min(1).max(65535);\n\nfunction parseFlags(argv: string[]): ParsedFlags {\n const { values } = parseArgs({ args: argv, strict: true, options: {\n 'base-url': { type: 'string' },\n host: { type: 'string' },\n port: { type: 'string' },\n label: { type: 'string' },\n workspace: { type: 'string' },\n 'relay-id': { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n } });\n if (values.help) {\n printHelp();\n process.exit(0);\n }\n const port = values.port === undefined ? undefined : Port.safeParse(values.port);\n if (port && !port.success) throw new Error(`Invalid --port value: ${values.port}`);\n return { baseUrl: values['base-url'], host: values.host, port: port?.data, label: values.label,\n workspace: values.workspace, relayId: values['relay-id'] };\n}\n\nfunction printHelp(): void {\n process.stdout.write(`yichus-mcp [options]\n\nYichus MCP server: bundles the WebMCP local relay with three static tools\n(yichus_list_calculators, yichus_open_calculator, yichus_save_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:\n\n {\n \"mcpServers\": {\n \"yichus\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@yichus/mcp@0.1.2\"]\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","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, type OpenAttempt } from './openUrl.js';\nimport { CoalescingRelayServer, FirstTabNamingRegistry, PageAnswerBridge } from './relay.js';\nimport {saveCalculator, saveCalculatorInput} from './saveCalculator.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.\nexport const PACKAGE_VERSION = '0.1.2';\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 Yichus calculator discovery, opening, and saving tools\n * 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 bridge = new PageAnswerBridge({\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 // Cold browser compilation can outlast the relay's 25-second default.\n // Keep a deadline so an unresponsive tab still fails explicitly.\n invokeTimeoutMs: 120000,\n }, new FirstTabNamingRegistry());\n const server = new CoalescingRelayServer({\n serverName: PACKAGE_NAME,\n serverVersion: PACKAGE_VERSION,\n bridge,\n }, TOOL_LIST_CHANGED_WINDOW_MS);\n\n registerYichusTools(server, { baseUrl, disableOpener: options.disableOpener ?? false });\n answerRefusalsAsThePageWroteThem(server, bridge);\n return server;\n}\n\n/**\n * What an agent hears when it connects: what the open pages offer, and\n * where to go instead to build the person's own app.\n */\nexport const relayInstructions = (baseUrl: string) => `Yichus in the browser: the tools here come from ${new URL(baseUrl).host} pages open in the person's browser, such as a calculator, the playground or the workbench. yichus_list_calculators and yichus_open_calculator open one, webmcp_list_tools lists what the open tabs offer, and each page's tools say what they do. Use them to explore a page with the person and explain what it shows.\n\nTo build the person's own app with them, use the Yichus studio instead. It is its own MCP server, run in their project folder (npx -y yichus@0.3.1 studio). It asks the person the product questions, checks the program after every change, and keeps a page open where you can point at what you mean while you talk.`;\n\n/**\n * How long one tools/list_changed notice waits for the rest of a tab's\n * registration before it goes out.\n */\nexport const TOOL_LIST_CHANGED_WINDOW_MS = 100;\n\n/**\n * Where a tool sits beside the relay's own. `@mcp-b/webmcp-local-relay`\n * has no public way to add one (its `registerStaticTool` is private), so\n * the two fields that method writes are read here, from the exactly pinned\n * version, and checked when the server is made: a release that renames\n * them fails construction instead of an agent's call.\n */\ninterface StaticToolHost {\n mcpServer: {\n server: { _instructions?: string };\n _registeredTools: { webmcp_call_tool: { handler: RelayToolHandler } };\n registerTool: (\n name: string,\n config: Record<string, unknown>,\n handler: (args: never) => Promise<unknown> | unknown\n ) => unknown;\n };\n staticToolData: Map<string, { name: string; description?: string; inputSchema: object; annotations?: object }>;\n}\n\ntype RelayToolHandler = (...call: unknown[]) => Promise<unknown>;\nconst isFunction = (field: unknown) => typeof field === 'function';\n\nconst StaticToolFields = z.object({\n mcpServer: z.object({\n // The MCP SDK's Server keeps the handshake instructions here; the relay\n // takes none of its own. server.test reads them back from initialize, so\n // a release that moves them fails there.\n server: z.custom(value => typeof value === 'object' && value !== null && '_instructions' in value),\n registerTool: z.custom(isFunction),\n // The MCP SDK's record of each registered tool; the relay's\n // webmcp_call_tool is wrapped there (answerRefusalsAsThePageWroteThem).\n _registeredTools: z.object({ webmcp_call_tool: z.object({ handler: z.custom(isFunction) }) }),\n }),\n staticToolData: z.instanceof(Map),\n});\nconst StaticToolHost = z.custom<StaticToolHost>(\n (value) => StaticToolFields.safeParse(value).success,\n { error: '@mcp-b/webmcp-local-relay no longer has mcpServer.registerTool, a registered webmcp_call_tool handler, an SDK server with instructions and staticToolData' },\n);\n\nfunction registerYichusTools(\n server: LocalRelayMcpServer,\n ctx: { baseUrl: string; disableOpener: boolean }\n): void {\n const host = StaticToolHost.parse(server);\n host.mcpServer.server._instructions = relayInstructions(ctx.baseUrl);\n\n registerStatic(host, {\n name:'yichus_save_calculator',\n description:'Save a browser-compiled example and its Bosatsu sources to a NEW local directory. Pass the public yichus_example_export tool name for the intended tab and its current source revision. Transfers bounded chunks internally and returns file paths, not generated HTML. Does not execute the program. Requires the browser tab to stay open.',\n annotations:{readOnlyHint:false,destructiveHint:false},\n input:saveCalculatorInput,\n handler:async args => {\n try {\n const payload = await saveCalculator(args,(name,params) => server.bridge.invokeTool(name,params));\n return {content:[{type:'text',text:JSON.stringify(payload)}],structuredContent:payload};\n } catch (error) {\n return {isError:true,content:[{type:'text',text:String(error)}]};\n }\n },\n });\n\n registerStatic(host, {\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 input: z.object({}),\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(host, {\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 input: z.object({\n slug: z.string().describe(\n 'Calculator slug from yichus_list_calculators (for example \"loan\", \"investment\", \"tax\", \"playground\").'\n ),\n }),\n handler: async (args) => {\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 const attempt: OpenAttempt | null = ctx.disableOpener ? null : await openUrlInBrowser(url);\n const payload = {\n calculator: {\n slug: calc.slug,\n title: calc.title,\n description: calc.description,\n },\n url,\n opened: attempt?.opened ?? false,\n opener: attempt && { command: attempt.command, args: attempt.args },\n ...(attempt?.error === undefined ? {} : { openerError: attempt.error }),\n };\n return {\n content: [{ type: 'text', text: openAnswer(calc, url, attempt) }],\n structuredContent: payload,\n };\n },\n });\n}\n\n/** The sentence an agent relays: what happened, and the link to open by hand when no browser was opened. */\nfunction openAnswer(calc: Calculator, url: string, attempt: OpenAttempt | null): string {\n if (attempt === null) return `Would open ${calc.title} at ${url} (browser opener disabled).`;\n if (attempt.opened) return `Opening ${calc.title} at ${url}.`;\n return `Could not open a browser on this machine (${attempt.error}). ` +\n `Open this link in a browser yourself to load ${calc.title}: ${url}`;\n}\n\n/**\n * Hands a page's refusal to the agent as the page wrote it.\n *\n * The relay's `webmcp_call_tool` adds its tool catalog to every page\n * answer. On a result that worked that is a reminder of what else can be\n * called; on a refusal (\"Loan Principal ($) (principal): 5000000 is above\n * the maximum 1000000 ...\") it buried the one sentence the agent had to\n * relay. The bridge kept the content each page sent, keyed by the answer\n * the relay extends; a refusal is answered with that content alone. The\n * relay's own errors (an unknown tool name, a page that never answered) are\n * not page answers and keep the catalog, which lists the names to use.\n */\nfunction answerRefusalsAsThePageWroteThem(server: LocalRelayMcpServer, bridge: PageAnswerBridge): void {\n const callTool = StaticToolHost.parse(server).mcpServer._registeredTools.webmcp_call_tool;\n const relayHandler = callTool.handler;\n callTool.handler = async (...call) => {\n const result = (await relayHandler(...call)) as { isError?: boolean };\n const page = bridge.pageAnswer(result);\n return result.isError === true && page !== undefined ? { ...result, content: page } : result;\n };\n}\n\ninterface StaticToolSpec<Input extends z.ZodObject> {\n name: string;\n description: string;\n annotations: Record<string, unknown>;\n input: Input;\n handler: (args: z.infer<Input>) => Promise<unknown> | unknown;\n}\n\n// The MCP SDK validates calls against the input's shape; agents read the\n// JSON Schema Zod derives from the same input.\nfunction registerStatic<Input extends z.ZodObject>(host: StaticToolHost, spec: StaticToolSpec<Input>): void {\n const { name, description, annotations, input, handler } = spec;\n host.mcpServer.registerTool(name, { description, inputSchema: input.shape, annotations }, handler);\n host.staticToolData.set(name, {\n name,\n description,\n inputSchema: z.toJSONSchema(input, { unrepresentable: 'any' }),\n annotations,\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};\n","/**\n * Curated catalogue of Yichus simulation demos.\n *\n * The entries are data: `calculators.json` is generated from\n * deploy/manifest.json by `yichus deploy --emit-calculators`, so the manifest\n * row that deploys a calculator is also its catalog entry and the two cannot\n * drift.\n */\nimport { z } from 'zod';\nimport catalog from './calculators.json' with { type: 'json' };\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 CalculatorEntry = z.strictObject({\n slug: z.string(),\n title: z.string(),\n description: z.string(),\n path: z.string(),\n});\n\nconst DEFAULT_BASE_URL = 'https://yich.us';\n\nexport const CALCULATORS: readonly Calculator[] = z.array(CalculatorEntry).parse(catalog);\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","[\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\": \"rainwater-history\",\n \"title\": \"Rainwater through a dry spell\",\n \"description\": \"Run NOAA weather observations, or your own daily rainfall CSV, through an inspectable daily tank model. Change capacity and demand, inspect captured data, and share a saved report.\",\n \"path\": \"demos/simulation/rainwater-history.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]\n","import { spawn } from 'node:child_process';\n\n/** What happened when the relay asked the platform opener for a URL. */\nexport interface OpenAttempt {\n command: string;\n args: string[];\n /** True once the opener process started; false when it could not be started. */\n opened: boolean;\n /** Why the opener could not be started (for example `spawn xdg-open ENOENT`). */\n error?: string;\n}\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 *\n * Resolves only once the opener has started or failed to start, so the\n * answer reports what happened. A machine with no opener (a container, a\n * headless box) answers `opened: false` instead of an unhandled `error`\n * event, which would end the relay and every tool with it.\n */\nexport function openUrlInBrowser(rawUrl: string): Promise<OpenAttempt> {\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 return new Promise((resolve) => {\n const child = spawn(command, args, { detached: true, stdio: 'ignore' });\n child.once('spawn', () => {\n child.unref();\n resolve({ command, args, opened: true });\n });\n // Node emits a failed start as an 'error' event; with no listener it\n // is thrown and ends the process.\n child.on('error', (error) => resolve({ command, args, opened: false, error: error.message }));\n });\n}\n","import {\n LocalRelayMcpServer,\n RelayBridgeServer,\n RelayRegistry,\n buildPublicToolName,\n type AggregatedTool,\n type RelayCallToolResult,\n type ResolvedInvocation,\n} from '@mcp-b/webmcp-local-relay';\n\ntype Hello = Parameters<RelayRegistry['upsertSource']>[1];\ntype InvocationQuery = Parameters<RelayRegistry['resolveInvocation']>[0];\ntype Transport = Parameters<LocalRelayMcpServer['connect']>[0];\ntype Message = Parameters<Transport['send']>[0];\ntype SendOptions = Parameters<Transport['send']>[1];\n\n/**\n * How many tab ids the registry remembers once their tabs are gone. A tab\n * that comes back while remembered (a reload) keeps its place; one that\n * returns after this many others is ordered as a new tab.\n */\nexport const REMEMBERED_TABS = 256;\n\n/**\n * Keeps the name an agent is already using when a second tab publishes the\n * same tool.\n *\n * The relay's own registry renames every copy of a tool once two tabs\n * publish it (`yichus_page_feedback` becomes `yichus_page_feedback_494e`\n * and `yichus_page_feedback_769c`), so opening a second site page broke the\n * name the agent had just called on the first. Here the tab the relay saw\n * first keeps the plain name and only later tabs keep the relay's tab-id\n * suffix. \"First\" is by tab id, which the embed keeps in the tab's\n * sessionStorage, so a reload of the first tab keeps its name too; when\n * that tab closes, the next-oldest remaining tab takes the plain name.\n *\n * Only the registry's public methods are refined: the relay still names,\n * ranks and resolves every tool, and the names agents see and call are\n * mapped onto its names in both directions.\n */\nexport class FirstTabNamingRegistry extends RelayRegistry {\n private readonly firstSeen = new Map<string, number>();\n private readonly tabByConnection = new Map<string, string>();\n private seen = 0;\n\n override upsertSource(connectionId: string, hello: Hello): void {\n super.upsertSource(connectionId, hello);\n this.tabByConnection.set(connectionId, hello.tabId);\n this.remember(hello.tabId);\n }\n\n override removeConnection(connectionId: string): void {\n super.removeConnection(connectionId);\n this.tabByConnection.delete(connectionId);\n }\n\n override listTools(): AggregatedTool[] {\n return this.named()\n .map(({ tool }) => tool)\n .sort((a, b) => a.name.localeCompare(b.name));\n }\n\n override resolveInvocation(options: InvocationQuery): ResolvedInvocation | null {\n const entry = this.named().find(({ tool }) => tool.name === options.toolName);\n if (!entry) return null;\n const resolved = super.resolveInvocation({ ...options, toolName: entry.relayName });\n return resolved && { ...resolved, publicToolName: entry.tool.name };\n }\n\n // Each relay tool with the name an agent sees: the plain name for the\n // copy published by the first-seen tab among those publishing it.\n private named(): { relayName: string; tool: AggregatedTool }[] {\n const tools = super.listTools();\n const owners = new Map<string, number>();\n for (const tool of tools) {\n for (const source of tool.sources) {\n const order = this.order(source.tabId);\n owners.set(tool.originalName, Math.min(owners.get(tool.originalName) ?? order, order));\n }\n }\n return tools.map((tool) => {\n const owned = tool.sources.some((source) => this.order(source.tabId) === owners.get(tool.originalName));\n const name = owned ? buildPublicToolName({ originalToolName: tool.originalName }) : tool.name;\n return { relayName: tool.name, tool: { ...tool, name } };\n });\n }\n\n private order(tabId: string): number {\n return this.firstSeen.get(tabId) ?? Number.POSITIVE_INFINITY;\n }\n\n private remember(tabId: string): void {\n if (this.firstSeen.has(tabId)) return;\n this.firstSeen.set(tabId, this.seen++);\n const open = new Set(this.tabByConnection.values());\n for (const tab of this.firstSeen.keys()) {\n if (this.firstSeen.size - open.size <= REMEMBERED_TABS) break;\n if (!open.has(tab)) this.firstSeen.delete(tab);\n }\n }\n}\n\nconst TOOL_LIST_CHANGED = 'notifications/tools/list_changed';\n\nconst isToolListChanged = (message: Message): boolean =>\n 'method' in message && message.method === TOOL_LIST_CHANGED && !('id' in message);\n\n/**\n * A transport that sends one `notifications/tools/list_changed` per burst.\n *\n * The relay mirrors each page tool as its own MCP tool, and the MCP SDK\n * sends a list_changed notice on every `registerTool` and every `remove()`;\n * the relay then sends one more of its own after each sync. A tab\n * publishing 12 tools therefore cost the agent 13 notices, and a reload (12\n * removals, 12 registrations) 25 or more, each of which many hosts answer\n * by re-reading the whole tool list. Here the first notice of a burst opens\n * a window of `windowMs` that the rest join; when it ends a single notice\n * goes out, after every registration in it has landed, so the tools/list\n * the agent sends in reply sees all of them. Closing drops a pending\n * notice. Every other message passes straight through.\n */\nexport class ToolListChangedCoalescer implements Transport {\n onclose?: Transport['onclose'];\n onerror?: Transport['onerror'];\n onmessage?: Transport['onmessage'];\n private pending: ReturnType<typeof setTimeout> | null = null;\n\n constructor(private readonly inner: Transport, private readonly windowMs: number) {\n inner.onmessage = (message, extra) => this.onmessage?.(message, extra);\n inner.onerror = (error) => this.onerror?.(error);\n inner.onclose = () => {\n this.drop();\n this.onclose?.();\n };\n }\n\n get sessionId(): string | undefined {\n return this.inner.sessionId;\n }\n\n setProtocolVersion(version: string): void {\n this.inner.setProtocolVersion?.(version);\n }\n\n start(): Promise<void> {\n return this.inner.start();\n }\n\n async send(message: Message, options?: SendOptions): Promise<void> {\n if (!isToolListChanged(message)) return this.inner.send(message, options);\n if (this.pending) return;\n this.pending = setTimeout(() => {\n this.pending = null;\n this.inner.send(message, options).catch((error: Error) => this.onerror?.(error));\n }, this.windowMs);\n this.pending.unref?.();\n }\n\n close(): Promise<void> {\n this.drop();\n return this.inner.close();\n }\n\n private drop(): void {\n if (this.pending) clearTimeout(this.pending);\n this.pending = null;\n }\n}\n\n/** The relay's MCP server, with list_changed notices coalesced on any transport it connects to. */\nexport class CoalescingRelayServer extends LocalRelayMcpServer {\n constructor(\n options: ConstructorParameters<typeof LocalRelayMcpServer>[0],\n private readonly windowMs: number,\n ) {\n super(options);\n }\n\n override connect(transport: Transport): Promise<void> {\n return super.connect(new ToolListChangedCoalescer(transport, this.windowMs));\n }\n}\n\n/**\n * A bridge that remembers what each page answered.\n *\n * The relay's `webmcp_call_tool` appends its whole tool catalog to a page's\n * answer by replacing the answer's content, the page's own refusals\n * included: \"Count (count): 100 is above the maximum 10\" arrived followed by\n * a page of tool descriptions. Here the content the page sent is kept,\n * keyed by the answer object the relay goes on to extend, so a refusal can\n * be handed to the agent exactly as the page wrote it\n * (`answerRefusalsAsThePageWroteThem` in server.ts).\n */\nexport class PageAnswerBridge extends RelayBridgeServer {\n private readonly pageContent = new WeakMap<object, RelayCallToolResult['content']>();\n\n override async invokeTool(...call: Parameters<RelayBridgeServer['invokeTool']>): Promise<RelayCallToolResult> {\n const result = await super.invokeTool(...call);\n this.pageContent.set(result, result.content);\n return result;\n }\n\n /** The content a page answered with, for an answer this bridge carried. */\n pageAnswer(result: object): RelayCallToolResult['content'] | undefined {\n return this.pageContent.get(result);\n }\n}\n","import {mkdir, writeFile} from 'node:fs/promises';\nimport {isAbsolute, join} from 'node:path';\nimport {z} from 'zod';\n\nexport const saveCalculatorInput = z.object({\n exportToolName: z.string().regex(/^yichus_example_export(?:_[a-zA-Z0-9_-]+)?$/).describe('Public export tool name from webmcp_list_tools, matched to the intended tab.'),\n exampleId: z.string().min(1),\n expectedRevision: z.number().int().nonnegative(),\n outputDirectory: z.string().refine(isAbsolute, 'Use an absolute output directory.').describe('A new directory under an existing parent. Existing directories are refused; files are never overwritten.'),\n});\n\nconst chunkSchema = z.object({\n exampleId: z.string(), revision: z.number().int(), exportId: z.string().min(1), assetBaseUrl: z.url(), selfContained: z.boolean().default(false),\n sources: z.array(z.object({fileName:z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*\\.bosatsu$/), source:z.string().max(1000000)})).min(1).max(16),\n html: z.object({fileName:z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*\\.html$/), content:z.string().min(1).max(32768),\n offset:z.number().int().nonnegative(), totalLength:z.number().int().positive().max(32*1024*1024), nextOffset:z.number().int().nonnegative().nullable()}),\n});\ntype Chunk = z.infer<typeof chunkSchema>;\ntype Invoke = (name:string, args:Record<string,unknown>) => Promise<unknown>;\n\n/** Transfer bytes through the relay without putting generated HTML in model context.\n * Validate the complete export before creating a directory. Chunks belong to one\n * compilation, and only its actual generated files are written (no execution).\n */\nexport async function saveCalculator(rawArgs:unknown, invoke:Invoke) {\n const args = saveCalculatorInput.parse(rawArgs);\n const content: string[] = [];\n let first: Chunk | undefined;\n let offset = 0;\n for (;;) {\n const response = z.object({isError:z.boolean().optional(),structuredContent:z.unknown().optional()}).parse(await invoke(args.exportToolName, {\n exampleId:args.exampleId, expectedRevision:args.expectedRevision, offset,\n ...(first ? {expectedExportId:first.exportId} : {}),\n }));\n if (response.isError) throw new Error('Browser export failed. Read the current example feedback before retrying.');\n const chunk = chunkSchema.parse(response.structuredContent);\n if (chunk.exampleId !== args.exampleId || chunk.revision !== args.expectedRevision || chunk.html.offset !== offset) throw new Error('Export does not match the requested example, revision, or offset.');\n if (first && (chunk.exportId !== first.exportId || chunk.assetBaseUrl !== first.assetBaseUrl || chunk.selfContained !== first.selfContained || chunk.html.fileName !== first.html.fileName || chunk.html.totalLength !== first.html.totalLength || JSON.stringify(chunk.sources) !== JSON.stringify(first.sources))) throw new Error('Export changed during transfer. Nothing was saved; start again.');\n first ??= chunk;\n const end = offset + chunk.html.content.length;\n if (end > chunk.html.totalLength || (chunk.html.nextOffset === null ? end !== chunk.html.totalLength : chunk.html.nextOffset !== end || end >= chunk.html.totalLength)) throw new Error('Incomplete or inconsistent export chunk. Nothing was saved.');\n content.push(chunk.html.content);\n if (chunk.html.nextOffset === null) break;\n offset = chunk.html.nextOffset;\n }\n const files = [...first.sources.map(s => ({fileName:s.fileName,content:s.source})),{fileName:first.html.fileName,content:content.join('')}];\n if (new Set(files.map(f => f.fileName.toLowerCase())).size !== files.length) throw new Error('Export file names must be unique.');\n await mkdir(args.outputDirectory);\n for (const file of files) await writeFile(join(args.outputDirectory,file.fileName),file.content,{encoding:'utf8',flag:'wx'});\n return {ok:true,directory:args.outputDirectory,revision:first.revision,exportId:first.exportId,...(first.selfContained ? {} : {assetBaseUrl:first.assetBaseUrl}),\n files:files.map(f => ({fileName:f.fileName,path:join(args.outputDirectory,f.fileName),bytes:Buffer.byteLength(f.content)})),\n htmlPath:join(args.outputDirectory,first.html.fileName),\n note:first.selfContained\n ? 'The HTML starts from configured defaults and carries its own scripts and styles, so it can be hosted anywhere and calls no Yichus server. Linking an input to a formula loads the formula compiler from jsDelivr on first use. No program was executed.'\n : 'The HTML starts from configured defaults and loads supporting assets from the site. It requires network access. No program was executed.',\n };\n}\n"],"mappings":"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","names":["z","z","z","z","z"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/server.ts","../src/calculators.ts","../src/calculators.json","../src/openUrl.ts","../src/relay.ts","../src/saveCalculator.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from 'node:util';\nimport { z } from 'zod';\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\nconst Port = z.coerce.number().int().min(1).max(65535);\n\nfunction parseFlags(argv: string[]): ParsedFlags {\n const { values } = parseArgs({ args: argv, strict: true, options: {\n 'base-url': { type: 'string' },\n host: { type: 'string' },\n port: { type: 'string' },\n label: { type: 'string' },\n workspace: { type: 'string' },\n 'relay-id': { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n } });\n if (values.help) {\n printHelp();\n process.exit(0);\n }\n const port = values.port === undefined ? undefined : Port.safeParse(values.port);\n if (port && !port.success) throw new Error(`Invalid --port value: ${values.port}`);\n return { baseUrl: values['base-url'], host: values.host, port: port?.data, label: values.label,\n workspace: values.workspace, relayId: values['relay-id'] };\n}\n\nfunction printHelp(): void {\n process.stdout.write(`yichus-mcp [options]\n\nYichus MCP server: bundles the WebMCP local relay with three static tools\n(yichus_list_calculators, yichus_open_calculator, yichus_save_calculator) so an MCP client\ncan drive any deployed Yichus data app 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:\n\n {\n \"mcpServers\": {\n \"yichus\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@yichus/mcp@0.1.3\"]\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","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, type OpenAttempt } from './openUrl.js';\nimport { CoalescingRelayServer, FirstTabNamingRegistry, PageAnswerBridge } from './relay.js';\nimport {saveCalculator, saveCalculatorInput} from './saveCalculator.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.\nexport const PACKAGE_VERSION = '0.1.3';\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 Yichus calculator discovery, opening, and saving tools\n * 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 bridge = new PageAnswerBridge({\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 // Cold browser compilation can outlast the relay's 25-second default.\n // Keep a deadline so an unresponsive tab still fails explicitly.\n invokeTimeoutMs: 120000,\n }, new FirstTabNamingRegistry());\n const server = new CoalescingRelayServer({\n serverName: PACKAGE_NAME,\n serverVersion: PACKAGE_VERSION,\n bridge,\n }, TOOL_LIST_CHANGED_WINDOW_MS);\n\n registerYichusTools(server, { baseUrl, disableOpener: options.disableOpener ?? false });\n answerRefusalsAsThePageWroteThem(server, bridge);\n return server;\n}\n\n/**\n * What an agent hears when it connects: what the open pages offer, and\n * where to go instead to build the person's own app.\n */\nexport const relayInstructions = (baseUrl: string) => `Yichus in the browser: the tools here come from ${new URL(baseUrl).host} pages open in the person's browser, such as a data app, the playground or the workbench. yichus_list_calculators and yichus_open_calculator open one, webmcp_list_tools lists what the open tabs offer, and each page's tools say what they do. Use them to explore a page with the person and explain what it shows.\n\nTo build the person's own app with them, use the Yichus studio instead. It is its own MCP server, run in their project folder (npx -y yichus@0.3.4 studio). It asks the person the product questions, checks the program after every change, and keeps a page open where you can point at what you mean while you talk.`;\n\n/**\n * How long one tools/list_changed notice waits for the rest of a tab's\n * registration before it goes out.\n */\nexport const TOOL_LIST_CHANGED_WINDOW_MS = 100;\n\n/**\n * Where a tool sits beside the relay's own. `@mcp-b/webmcp-local-relay`\n * has no public way to add one (its `registerStaticTool` is private), so\n * the two fields that method writes are read here, from the exactly pinned\n * version, and checked when the server is made: a release that renames\n * them fails construction instead of an agent's call.\n */\ninterface StaticToolHost {\n mcpServer: {\n server: { _instructions?: string };\n _registeredTools: { webmcp_call_tool: { handler: RelayToolHandler } };\n registerTool: (\n name: string,\n config: Record<string, unknown>,\n handler: (args: never) => Promise<unknown> | unknown\n ) => unknown;\n };\n staticToolData: Map<string, { name: string; description?: string; inputSchema: object; annotations?: object }>;\n}\n\ntype RelayToolHandler = (...call: unknown[]) => Promise<unknown>;\nconst isFunction = (field: unknown) => typeof field === 'function';\n\nconst StaticToolFields = z.object({\n mcpServer: z.object({\n // The MCP SDK's Server keeps the handshake instructions here; the relay\n // takes none of its own. server.test reads them back from initialize, so\n // a release that moves them fails there.\n server: z.custom(value => typeof value === 'object' && value !== null && '_instructions' in value),\n registerTool: z.custom(isFunction),\n // The MCP SDK's record of each registered tool; the relay's\n // webmcp_call_tool is wrapped there (answerRefusalsAsThePageWroteThem).\n _registeredTools: z.object({ webmcp_call_tool: z.object({ handler: z.custom(isFunction) }) }),\n }),\n staticToolData: z.instanceof(Map),\n});\nconst StaticToolHost = z.custom<StaticToolHost>(\n (value) => StaticToolFields.safeParse(value).success,\n { error: '@mcp-b/webmcp-local-relay no longer has mcpServer.registerTool, a registered webmcp_call_tool handler, an SDK server with instructions and staticToolData' },\n);\n\nfunction registerYichusTools(\n server: LocalRelayMcpServer,\n ctx: { baseUrl: string; disableOpener: boolean }\n): void {\n const host = StaticToolHost.parse(server);\n host.mcpServer.server._instructions = relayInstructions(ctx.baseUrl);\n\n registerStatic(host, {\n name:'yichus_save_calculator',\n description:'Save a browser-compiled example and its Bosatsu sources to a NEW local directory. Pass the public yichus_example_export tool name for the intended tab and its current source revision. Transfers bounded chunks internally and returns file paths, not generated HTML. Does not execute the program. Requires the browser tab to stay open.',\n annotations:{readOnlyHint:false,destructiveHint:false},\n input:saveCalculatorInput,\n handler:async args => {\n try {\n const payload = await saveCalculator(args,(name,params) => server.bridge.invokeTool(name,params));\n return {content:[{type:'text',text:JSON.stringify(payload)}],structuredContent:payload};\n } catch (error) {\n return {isError:true,content:[{type:'text',text:String(error)}]};\n }\n },\n });\n\n registerStatic(host, {\n name: 'yichus_list_calculators',\n description:\n 'List the curated Yichus data app 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 input: z.object({}),\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(host, {\n name: 'yichus_open_calculator',\n description:\n \"Open a curated Yichus data app (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 data apps; yichus_playground_* for the playground). Use yichus_list_calculators to discover slugs.\",\n annotations: { openWorldHint: true },\n input: z.object({\n slug: z.string().describe(\n 'Data app slug from yichus_list_calculators (for example \"loan\", \"investment\", \"tax\", \"playground\").'\n ),\n }),\n handler: async (args) => {\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 data app slug \"${args.slug}\". Known slugs: ${known}.`,\n },\n ],\n isError: true,\n };\n }\n const url = withWebMcpFlag(calculatorUrl(calc, ctx.baseUrl));\n const attempt: OpenAttempt | null = ctx.disableOpener ? null : await openUrlInBrowser(url);\n const payload = {\n calculator: {\n slug: calc.slug,\n title: calc.title,\n description: calc.description,\n },\n url,\n opened: attempt?.opened ?? false,\n opener: attempt && { command: attempt.command, args: attempt.args },\n ...(attempt?.error === undefined ? {} : { openerError: attempt.error }),\n };\n return {\n content: [{ type: 'text', text: openAnswer(calc, url, attempt) }],\n structuredContent: payload,\n };\n },\n });\n}\n\n/** The sentence an agent relays: what happened, and the link to open by hand when no browser was opened. */\nfunction openAnswer(calc: Calculator, url: string, attempt: OpenAttempt | null): string {\n if (attempt === null) return `Would open ${calc.title} at ${url} (browser opener disabled).`;\n if (attempt.opened) return `Opening ${calc.title} at ${url}.`;\n return `Could not open a browser on this machine (${attempt.error}). ` +\n `Open this link in a browser yourself to load ${calc.title}: ${url}`;\n}\n\n/**\n * Hands a page's refusal to the agent as the page wrote it.\n *\n * The relay's `webmcp_call_tool` adds its tool catalog to every page\n * answer. On a result that worked that is a reminder of what else can be\n * called; on a refusal (\"Loan Principal ($) (principal): 5000000 is above\n * the maximum 1000000 ...\") it buried the one sentence the agent had to\n * relay. The bridge kept the content each page sent, keyed by the answer\n * the relay extends; a refusal is answered with that content alone. The\n * relay's own errors (an unknown tool name, a page that never answered) are\n * not page answers and keep the catalog, which lists the names to use.\n */\nfunction answerRefusalsAsThePageWroteThem(server: LocalRelayMcpServer, bridge: PageAnswerBridge): void {\n const callTool = StaticToolHost.parse(server).mcpServer._registeredTools.webmcp_call_tool;\n const relayHandler = callTool.handler;\n callTool.handler = async (...call) => {\n const result = (await relayHandler(...call)) as { isError?: boolean };\n const page = bridge.pageAnswer(result);\n return result.isError === true && page !== undefined ? { ...result, content: page } : result;\n };\n}\n\ninterface StaticToolSpec<Input extends z.ZodObject> {\n name: string;\n description: string;\n annotations: Record<string, unknown>;\n input: Input;\n handler: (args: z.infer<Input>) => Promise<unknown> | unknown;\n}\n\n// The MCP SDK validates calls against the input's shape; agents read the\n// JSON Schema Zod derives from the same input.\nfunction registerStatic<Input extends z.ZodObject>(host: StaticToolHost, spec: StaticToolSpec<Input>): void {\n const { name, description, annotations, input, handler } = spec;\n host.mcpServer.registerTool(name, { description, inputSchema: input.shape, annotations }, handler);\n host.staticToolData.set(name, {\n name,\n description,\n inputSchema: z.toJSONSchema(input, { unrepresentable: 'any' }),\n annotations,\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};\n","/**\n * Curated catalogue of Yichus simulation demos.\n *\n * The entries are data: `calculators.json` is generated from\n * deploy/manifest.json by `yichus deploy --emit-calculators`, so the manifest\n * row that deploys a calculator is also its catalog entry and the two cannot\n * drift.\n */\nimport { z } from 'zod';\nimport catalog from './calculators.json' with { type: 'json' };\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 CalculatorEntry = z.strictObject({\n slug: z.string(),\n title: z.string(),\n description: z.string(),\n path: z.string(),\n});\n\nconst DEFAULT_BASE_URL = 'https://yich.us';\n\nexport const CALCULATORS: readonly Calculator[] = z.array(CalculatorEntry).parse(catalog);\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","[\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\": \"rainwater-history\",\n \"title\": \"Rainwater through a dry spell\",\n \"description\": \"Run NOAA weather observations, or your own daily rainfall CSV, through an inspectable daily tank model. Change capacity and demand, inspect captured data, and share a saved report.\",\n \"path\": \"demos/simulation/rainwater-history.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]\n","import { spawn } from 'node:child_process';\n\n/** What happened when the relay asked the platform opener for a URL. */\nexport interface OpenAttempt {\n command: string;\n args: string[];\n /** True once the opener process started; false when it could not be started. */\n opened: boolean;\n /** Why the opener could not be started (for example `spawn xdg-open ENOENT`). */\n error?: string;\n}\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 *\n * Resolves only once the opener has started or failed to start, so the\n * answer reports what happened. A machine with no opener (a container, a\n * headless box) answers `opened: false` instead of an unhandled `error`\n * event, which would end the relay and every tool with it.\n */\nexport function openUrlInBrowser(rawUrl: string): Promise<OpenAttempt> {\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 return new Promise((resolve) => {\n const child = spawn(command, args, { detached: true, stdio: 'ignore' });\n child.once('spawn', () => {\n child.unref();\n resolve({ command, args, opened: true });\n });\n // Node emits a failed start as an 'error' event; with no listener it\n // is thrown and ends the process.\n child.on('error', (error) => resolve({ command, args, opened: false, error: error.message }));\n });\n}\n","import {\n LocalRelayMcpServer,\n RelayBridgeServer,\n RelayRegistry,\n buildPublicToolName,\n type AggregatedTool,\n type RelayCallToolResult,\n type ResolvedInvocation,\n} from '@mcp-b/webmcp-local-relay';\n\ntype Hello = Parameters<RelayRegistry['upsertSource']>[1];\ntype InvocationQuery = Parameters<RelayRegistry['resolveInvocation']>[0];\ntype Transport = Parameters<LocalRelayMcpServer['connect']>[0];\ntype Message = Parameters<Transport['send']>[0];\ntype SendOptions = Parameters<Transport['send']>[1];\n\n/**\n * How many tab ids the registry remembers once their tabs are gone. A tab\n * that comes back while remembered (a reload) keeps its place; one that\n * returns after this many others is ordered as a new tab.\n */\nexport const REMEMBERED_TABS = 256;\n\n/**\n * Keeps the name an agent is already using when a second tab publishes the\n * same tool.\n *\n * The relay's own registry renames every copy of a tool once two tabs\n * publish it (`yichus_page_feedback` becomes `yichus_page_feedback_494e`\n * and `yichus_page_feedback_769c`), so opening a second site page broke the\n * name the agent had just called on the first. Here the tab the relay saw\n * first keeps the plain name and only later tabs keep the relay's tab-id\n * suffix. \"First\" is by tab id, which the embed keeps in the tab's\n * sessionStorage, so a reload of the first tab keeps its name too; when\n * that tab closes, the next-oldest remaining tab takes the plain name.\n *\n * Only the registry's public methods are refined: the relay still names,\n * ranks and resolves every tool, and the names agents see and call are\n * mapped onto its names in both directions.\n */\nexport class FirstTabNamingRegistry extends RelayRegistry {\n private readonly firstSeen = new Map<string, number>();\n private readonly tabByConnection = new Map<string, string>();\n private seen = 0;\n\n override upsertSource(connectionId: string, hello: Hello): void {\n super.upsertSource(connectionId, hello);\n this.tabByConnection.set(connectionId, hello.tabId);\n this.remember(hello.tabId);\n }\n\n override removeConnection(connectionId: string): void {\n super.removeConnection(connectionId);\n this.tabByConnection.delete(connectionId);\n }\n\n override listTools(): AggregatedTool[] {\n return this.named()\n .map(({ tool }) => tool)\n .sort((a, b) => a.name.localeCompare(b.name));\n }\n\n override resolveInvocation(options: InvocationQuery): ResolvedInvocation | null {\n const entry = this.named().find(({ tool }) => tool.name === options.toolName);\n if (!entry) return null;\n const resolved = super.resolveInvocation({ ...options, toolName: entry.relayName });\n return resolved && { ...resolved, publicToolName: entry.tool.name };\n }\n\n // Each relay tool with the name an agent sees: the plain name for the\n // copy published by the first-seen tab among those publishing it.\n private named(): { relayName: string; tool: AggregatedTool }[] {\n const tools = super.listTools();\n const owners = new Map<string, number>();\n for (const tool of tools) {\n for (const source of tool.sources) {\n const order = this.order(source.tabId);\n owners.set(tool.originalName, Math.min(owners.get(tool.originalName) ?? order, order));\n }\n }\n return tools.map((tool) => {\n const owned = tool.sources.some((source) => this.order(source.tabId) === owners.get(tool.originalName));\n const name = owned ? buildPublicToolName({ originalToolName: tool.originalName }) : tool.name;\n return { relayName: tool.name, tool: { ...tool, name } };\n });\n }\n\n private order(tabId: string): number {\n return this.firstSeen.get(tabId) ?? Number.POSITIVE_INFINITY;\n }\n\n private remember(tabId: string): void {\n if (this.firstSeen.has(tabId)) return;\n this.firstSeen.set(tabId, this.seen++);\n const open = new Set(this.tabByConnection.values());\n for (const tab of this.firstSeen.keys()) {\n if (this.firstSeen.size - open.size <= REMEMBERED_TABS) break;\n if (!open.has(tab)) this.firstSeen.delete(tab);\n }\n }\n}\n\nconst TOOL_LIST_CHANGED = 'notifications/tools/list_changed';\n\nconst isToolListChanged = (message: Message): boolean =>\n 'method' in message && message.method === TOOL_LIST_CHANGED && !('id' in message);\n\n/**\n * A transport that sends one `notifications/tools/list_changed` per burst.\n *\n * The relay mirrors each page tool as its own MCP tool, and the MCP SDK\n * sends a list_changed notice on every `registerTool` and every `remove()`;\n * the relay then sends one more of its own after each sync. A tab\n * publishing 12 tools therefore cost the agent 13 notices, and a reload (12\n * removals, 12 registrations) 25 or more, each of which many hosts answer\n * by re-reading the whole tool list. Here the first notice of a burst opens\n * a window of `windowMs` that the rest join; when it ends a single notice\n * goes out, after every registration in it has landed, so the tools/list\n * the agent sends in reply sees all of them. Closing drops a pending\n * notice. Every other message passes straight through.\n */\nexport class ToolListChangedCoalescer implements Transport {\n onclose?: Transport['onclose'];\n onerror?: Transport['onerror'];\n onmessage?: Transport['onmessage'];\n private pending: ReturnType<typeof setTimeout> | null = null;\n\n constructor(private readonly inner: Transport, private readonly windowMs: number) {\n inner.onmessage = (message, extra) => this.onmessage?.(message, extra);\n inner.onerror = (error) => this.onerror?.(error);\n inner.onclose = () => {\n this.drop();\n this.onclose?.();\n };\n }\n\n get sessionId(): string | undefined {\n return this.inner.sessionId;\n }\n\n setProtocolVersion(version: string): void {\n this.inner.setProtocolVersion?.(version);\n }\n\n start(): Promise<void> {\n return this.inner.start();\n }\n\n async send(message: Message, options?: SendOptions): Promise<void> {\n if (!isToolListChanged(message)) return this.inner.send(message, options);\n if (this.pending) return;\n this.pending = setTimeout(() => {\n this.pending = null;\n this.inner.send(message, options).catch((error: Error) => this.onerror?.(error));\n }, this.windowMs);\n this.pending.unref?.();\n }\n\n close(): Promise<void> {\n this.drop();\n return this.inner.close();\n }\n\n private drop(): void {\n if (this.pending) clearTimeout(this.pending);\n this.pending = null;\n }\n}\n\n/** The relay's MCP server, with list_changed notices coalesced on any transport it connects to. */\nexport class CoalescingRelayServer extends LocalRelayMcpServer {\n constructor(\n options: ConstructorParameters<typeof LocalRelayMcpServer>[0],\n private readonly windowMs: number,\n ) {\n super(options);\n }\n\n override connect(transport: Transport): Promise<void> {\n return super.connect(new ToolListChangedCoalescer(transport, this.windowMs));\n }\n}\n\n/**\n * A bridge that remembers what each page answered.\n *\n * The relay's `webmcp_call_tool` appends its whole tool catalog to a page's\n * answer by replacing the answer's content, the page's own refusals\n * included: \"Count (count): 100 is above the maximum 10\" arrived followed by\n * a page of tool descriptions. Here the content the page sent is kept,\n * keyed by the answer object the relay goes on to extend, so a refusal can\n * be handed to the agent exactly as the page wrote it\n * (`answerRefusalsAsThePageWroteThem` in server.ts).\n */\nexport class PageAnswerBridge extends RelayBridgeServer {\n private readonly pageContent = new WeakMap<object, RelayCallToolResult['content']>();\n\n override async invokeTool(...call: Parameters<RelayBridgeServer['invokeTool']>): Promise<RelayCallToolResult> {\n const result = await super.invokeTool(...call);\n this.pageContent.set(result, result.content);\n return result;\n }\n\n /** The content a page answered with, for an answer this bridge carried. */\n pageAnswer(result: object): RelayCallToolResult['content'] | undefined {\n return this.pageContent.get(result);\n }\n}\n","import {mkdir, writeFile} from 'node:fs/promises';\nimport {isAbsolute, join} from 'node:path';\nimport {z} from 'zod';\n\nexport const saveCalculatorInput = z.object({\n exportToolName: z.string().regex(/^yichus_example_export(?:_[a-zA-Z0-9_-]+)?$/).describe('Public export tool name from webmcp_list_tools, matched to the intended tab.'),\n exampleId: z.string().min(1),\n expectedRevision: z.number().int().nonnegative(),\n outputDirectory: z.string().refine(isAbsolute, 'Use an absolute output directory.').describe('A new directory under an existing parent. Existing directories are refused; files are never overwritten.'),\n});\n\nconst chunkSchema = z.object({\n exampleId: z.string(), revision: z.number().int(), exportId: z.string().min(1), assetBaseUrl: z.url(), selfContained: z.boolean().default(false),\n sources: z.array(z.object({fileName:z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*\\.bosatsu$/), source:z.string().max(1000000)})).min(1).max(16),\n html: z.object({fileName:z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*\\.html$/), content:z.string().min(1).max(32768),\n offset:z.number().int().nonnegative(), totalLength:z.number().int().positive().max(32*1024*1024), nextOffset:z.number().int().nonnegative().nullable()}),\n});\ntype Chunk = z.infer<typeof chunkSchema>;\ntype Invoke = (name:string, args:Record<string,unknown>) => Promise<unknown>;\n\n/** Transfer bytes through the relay without putting generated HTML in model context.\n * Validate the complete export before creating a directory. Chunks belong to one\n * compilation, and only its actual generated files are written (no execution).\n */\nexport async function saveCalculator(rawArgs:unknown, invoke:Invoke) {\n const args = saveCalculatorInput.parse(rawArgs);\n const content: string[] = [];\n let first: Chunk | undefined;\n let offset = 0;\n for (;;) {\n const response = z.object({isError:z.boolean().optional(),structuredContent:z.unknown().optional()}).parse(await invoke(args.exportToolName, {\n exampleId:args.exampleId, expectedRevision:args.expectedRevision, offset,\n ...(first ? {expectedExportId:first.exportId} : {}),\n }));\n if (response.isError) throw new Error('Browser export failed. Read the current example feedback before retrying.');\n const chunk = chunkSchema.parse(response.structuredContent);\n if (chunk.exampleId !== args.exampleId || chunk.revision !== args.expectedRevision || chunk.html.offset !== offset) throw new Error('Export does not match the requested example, revision, or offset.');\n if (first && (chunk.exportId !== first.exportId || chunk.assetBaseUrl !== first.assetBaseUrl || chunk.selfContained !== first.selfContained || chunk.html.fileName !== first.html.fileName || chunk.html.totalLength !== first.html.totalLength || JSON.stringify(chunk.sources) !== JSON.stringify(first.sources))) throw new Error('Export changed during transfer. Nothing was saved; start again.');\n first ??= chunk;\n const end = offset + chunk.html.content.length;\n if (end > chunk.html.totalLength || (chunk.html.nextOffset === null ? end !== chunk.html.totalLength : chunk.html.nextOffset !== end || end >= chunk.html.totalLength)) throw new Error('Incomplete or inconsistent export chunk. Nothing was saved.');\n content.push(chunk.html.content);\n if (chunk.html.nextOffset === null) break;\n offset = chunk.html.nextOffset;\n }\n const files = [...first.sources.map(s => ({fileName:s.fileName,content:s.source})),{fileName:first.html.fileName,content:content.join('')}];\n if (new Set(files.map(f => f.fileName.toLowerCase())).size !== files.length) throw new Error('Export file names must be unique.');\n await mkdir(args.outputDirectory);\n for (const file of files) await writeFile(join(args.outputDirectory,file.fileName),file.content,{encoding:'utf8',flag:'wx'});\n return {ok:true,directory:args.outputDirectory,revision:first.revision,exportId:first.exportId,...(first.selfContained ? {} : {assetBaseUrl:first.assetBaseUrl}),\n files:files.map(f => ({fileName:f.fileName,path:join(args.outputDirectory,f.fileName),bytes:Buffer.byteLength(f.content)})),\n htmlPath:join(args.outputDirectory,first.html.fileName),\n note:first.selfContained\n ? 'The HTML starts from configured defaults and carries its own scripts and styles, so it can be hosted anywhere and calls no Yichus server. Linking an input to a formula loads the formula compiler from jsDelivr on first use. No program was executed.'\n : 'The HTML starts from configured defaults and loads supporting assets from the site. It requires network access. No program was executed.',\n };\n}\n"],"mappings":"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","names":["z","z","z","z","z"]}
package/dist/index.js CHANGED
@@ -388,7 +388,7 @@ async function saveCalculator(rawArgs, invoke) {
388
388
 
389
389
  // src/server.ts
390
390
  var PACKAGE_NAME = "yichus-mcp";
391
- var PACKAGE_VERSION = "0.1.2";
391
+ var PACKAGE_VERSION = "0.1.3";
392
392
  function createYichusMcpServer(options = {}) {
393
393
  const baseUrl = normalizeBaseUrl(options.baseUrl ?? "https://yich.us");
394
394
  const bridgeOverrides = options.bridgeOverrides ?? {};
@@ -414,9 +414,9 @@ function createYichusMcpServer(options = {}) {
414
414
  answerRefusalsAsThePageWroteThem(server, bridge);
415
415
  return server;
416
416
  }
417
- var relayInstructions = (baseUrl) => `Yichus in the browser: the tools here come from ${new URL(baseUrl).host} pages open in the person's browser, such as a calculator, the playground or the workbench. yichus_list_calculators and yichus_open_calculator open one, webmcp_list_tools lists what the open tabs offer, and each page's tools say what they do. Use them to explore a page with the person and explain what it shows.
417
+ var relayInstructions = (baseUrl) => `Yichus in the browser: the tools here come from ${new URL(baseUrl).host} pages open in the person's browser, such as a data app, the playground or the workbench. yichus_list_calculators and yichus_open_calculator open one, webmcp_list_tools lists what the open tabs offer, and each page's tools say what they do. Use them to explore a page with the person and explain what it shows.
418
418
 
419
- To build the person's own app with them, use the Yichus studio instead. It is its own MCP server, run in their project folder (npx -y yichus@0.3.1 studio). It asks the person the product questions, checks the program after every change, and keeps a page open where you can point at what you mean while you talk.`;
419
+ To build the person's own app with them, use the Yichus studio instead. It is its own MCP server, run in their project folder (npx -y yichus@0.3.4 studio). It asks the person the product questions, checks the program after every change, and keeps a page open where you can point at what you mean while you talk.`;
420
420
  var TOOL_LIST_CHANGED_WINDOW_MS = 100;
421
421
  var isFunction = (field) => typeof field === "function";
422
422
  var StaticToolFields = z3.object({
@@ -455,7 +455,7 @@ function registerYichusTools(server, ctx) {
455
455
  });
456
456
  registerStatic(host, {
457
457
  name: "yichus_list_calculators",
458
- 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.",
458
+ description: "List the curated Yichus data app demos and the playground editor available on the configured Yichus origin. Returns a slug, title, description, and absolute URL for each.",
459
459
  annotations: { readOnlyHint: true, idempotentHint: true },
460
460
  input: z3.object({}),
461
461
  handler: async () => {
@@ -474,11 +474,11 @@ function registerYichusTools(server, ctx) {
474
474
  });
475
475
  registerStatic(host, {
476
476
  name: "yichus_open_calculator",
477
- 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.",
477
+ description: "Open a curated Yichus data app (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 data apps; yichus_playground_* for the playground). Use yichus_list_calculators to discover slugs.",
478
478
  annotations: { openWorldHint: true },
479
479
  input: z3.object({
480
480
  slug: z3.string().describe(
481
- 'Calculator slug from yichus_list_calculators (for example "loan", "investment", "tax", "playground").'
481
+ 'Data app slug from yichus_list_calculators (for example "loan", "investment", "tax", "playground").'
482
482
  )
483
483
  }),
484
484
  handler: async (args) => {
@@ -489,7 +489,7 @@ function registerYichusTools(server, ctx) {
489
489
  content: [
490
490
  {
491
491
  type: "text",
492
- text: `Unknown calculator slug "${args.slug}". Known slugs: ${known}.`
492
+ text: `Unknown data app slug "${args.slug}". Known slugs: ${known}.`
493
493
  }
494
494
  ],
495
495
  isError: true
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../src/calculators.ts","../src/calculators.json","../src/openUrl.ts","../src/relay.ts","../src/saveCalculator.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, type OpenAttempt } from './openUrl.js';\nimport { CoalescingRelayServer, FirstTabNamingRegistry, PageAnswerBridge } from './relay.js';\nimport {saveCalculator, saveCalculatorInput} from './saveCalculator.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.\nexport const PACKAGE_VERSION = '0.1.2';\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 Yichus calculator discovery, opening, and saving tools\n * 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 bridge = new PageAnswerBridge({\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 // Cold browser compilation can outlast the relay's 25-second default.\n // Keep a deadline so an unresponsive tab still fails explicitly.\n invokeTimeoutMs: 120000,\n }, new FirstTabNamingRegistry());\n const server = new CoalescingRelayServer({\n serverName: PACKAGE_NAME,\n serverVersion: PACKAGE_VERSION,\n bridge,\n }, TOOL_LIST_CHANGED_WINDOW_MS);\n\n registerYichusTools(server, { baseUrl, disableOpener: options.disableOpener ?? false });\n answerRefusalsAsThePageWroteThem(server, bridge);\n return server;\n}\n\n/**\n * What an agent hears when it connects: what the open pages offer, and\n * where to go instead to build the person's own app.\n */\nexport const relayInstructions = (baseUrl: string) => `Yichus in the browser: the tools here come from ${new URL(baseUrl).host} pages open in the person's browser, such as a calculator, the playground or the workbench. yichus_list_calculators and yichus_open_calculator open one, webmcp_list_tools lists what the open tabs offer, and each page's tools say what they do. Use them to explore a page with the person and explain what it shows.\n\nTo build the person's own app with them, use the Yichus studio instead. It is its own MCP server, run in their project folder (npx -y yichus@0.3.1 studio). It asks the person the product questions, checks the program after every change, and keeps a page open where you can point at what you mean while you talk.`;\n\n/**\n * How long one tools/list_changed notice waits for the rest of a tab's\n * registration before it goes out.\n */\nexport const TOOL_LIST_CHANGED_WINDOW_MS = 100;\n\n/**\n * Where a tool sits beside the relay's own. `@mcp-b/webmcp-local-relay`\n * has no public way to add one (its `registerStaticTool` is private), so\n * the two fields that method writes are read here, from the exactly pinned\n * version, and checked when the server is made: a release that renames\n * them fails construction instead of an agent's call.\n */\ninterface StaticToolHost {\n mcpServer: {\n server: { _instructions?: string };\n _registeredTools: { webmcp_call_tool: { handler: RelayToolHandler } };\n registerTool: (\n name: string,\n config: Record<string, unknown>,\n handler: (args: never) => Promise<unknown> | unknown\n ) => unknown;\n };\n staticToolData: Map<string, { name: string; description?: string; inputSchema: object; annotations?: object }>;\n}\n\ntype RelayToolHandler = (...call: unknown[]) => Promise<unknown>;\nconst isFunction = (field: unknown) => typeof field === 'function';\n\nconst StaticToolFields = z.object({\n mcpServer: z.object({\n // The MCP SDK's Server keeps the handshake instructions here; the relay\n // takes none of its own. server.test reads them back from initialize, so\n // a release that moves them fails there.\n server: z.custom(value => typeof value === 'object' && value !== null && '_instructions' in value),\n registerTool: z.custom(isFunction),\n // The MCP SDK's record of each registered tool; the relay's\n // webmcp_call_tool is wrapped there (answerRefusalsAsThePageWroteThem).\n _registeredTools: z.object({ webmcp_call_tool: z.object({ handler: z.custom(isFunction) }) }),\n }),\n staticToolData: z.instanceof(Map),\n});\nconst StaticToolHost = z.custom<StaticToolHost>(\n (value) => StaticToolFields.safeParse(value).success,\n { error: '@mcp-b/webmcp-local-relay no longer has mcpServer.registerTool, a registered webmcp_call_tool handler, an SDK server with instructions and staticToolData' },\n);\n\nfunction registerYichusTools(\n server: LocalRelayMcpServer,\n ctx: { baseUrl: string; disableOpener: boolean }\n): void {\n const host = StaticToolHost.parse(server);\n host.mcpServer.server._instructions = relayInstructions(ctx.baseUrl);\n\n registerStatic(host, {\n name:'yichus_save_calculator',\n description:'Save a browser-compiled example and its Bosatsu sources to a NEW local directory. Pass the public yichus_example_export tool name for the intended tab and its current source revision. Transfers bounded chunks internally and returns file paths, not generated HTML. Does not execute the program. Requires the browser tab to stay open.',\n annotations:{readOnlyHint:false,destructiveHint:false},\n input:saveCalculatorInput,\n handler:async args => {\n try {\n const payload = await saveCalculator(args,(name,params) => server.bridge.invokeTool(name,params));\n return {content:[{type:'text',text:JSON.stringify(payload)}],structuredContent:payload};\n } catch (error) {\n return {isError:true,content:[{type:'text',text:String(error)}]};\n }\n },\n });\n\n registerStatic(host, {\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 input: z.object({}),\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(host, {\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 input: z.object({\n slug: z.string().describe(\n 'Calculator slug from yichus_list_calculators (for example \"loan\", \"investment\", \"tax\", \"playground\").'\n ),\n }),\n handler: async (args) => {\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 const attempt: OpenAttempt | null = ctx.disableOpener ? null : await openUrlInBrowser(url);\n const payload = {\n calculator: {\n slug: calc.slug,\n title: calc.title,\n description: calc.description,\n },\n url,\n opened: attempt?.opened ?? false,\n opener: attempt && { command: attempt.command, args: attempt.args },\n ...(attempt?.error === undefined ? {} : { openerError: attempt.error }),\n };\n return {\n content: [{ type: 'text', text: openAnswer(calc, url, attempt) }],\n structuredContent: payload,\n };\n },\n });\n}\n\n/** The sentence an agent relays: what happened, and the link to open by hand when no browser was opened. */\nfunction openAnswer(calc: Calculator, url: string, attempt: OpenAttempt | null): string {\n if (attempt === null) return `Would open ${calc.title} at ${url} (browser opener disabled).`;\n if (attempt.opened) return `Opening ${calc.title} at ${url}.`;\n return `Could not open a browser on this machine (${attempt.error}). ` +\n `Open this link in a browser yourself to load ${calc.title}: ${url}`;\n}\n\n/**\n * Hands a page's refusal to the agent as the page wrote it.\n *\n * The relay's `webmcp_call_tool` adds its tool catalog to every page\n * answer. On a result that worked that is a reminder of what else can be\n * called; on a refusal (\"Loan Principal ($) (principal): 5000000 is above\n * the maximum 1000000 ...\") it buried the one sentence the agent had to\n * relay. The bridge kept the content each page sent, keyed by the answer\n * the relay extends; a refusal is answered with that content alone. The\n * relay's own errors (an unknown tool name, a page that never answered) are\n * not page answers and keep the catalog, which lists the names to use.\n */\nfunction answerRefusalsAsThePageWroteThem(server: LocalRelayMcpServer, bridge: PageAnswerBridge): void {\n const callTool = StaticToolHost.parse(server).mcpServer._registeredTools.webmcp_call_tool;\n const relayHandler = callTool.handler;\n callTool.handler = async (...call) => {\n const result = (await relayHandler(...call)) as { isError?: boolean };\n const page = bridge.pageAnswer(result);\n return result.isError === true && page !== undefined ? { ...result, content: page } : result;\n };\n}\n\ninterface StaticToolSpec<Input extends z.ZodObject> {\n name: string;\n description: string;\n annotations: Record<string, unknown>;\n input: Input;\n handler: (args: z.infer<Input>) => Promise<unknown> | unknown;\n}\n\n// The MCP SDK validates calls against the input's shape; agents read the\n// JSON Schema Zod derives from the same input.\nfunction registerStatic<Input extends z.ZodObject>(host: StaticToolHost, spec: StaticToolSpec<Input>): void {\n const { name, description, annotations, input, handler } = spec;\n host.mcpServer.registerTool(name, { description, inputSchema: input.shape, annotations }, handler);\n host.staticToolData.set(name, {\n name,\n description,\n inputSchema: z.toJSONSchema(input, { unrepresentable: 'any' }),\n annotations,\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};\n","/**\n * Curated catalogue of Yichus simulation demos.\n *\n * The entries are data: `calculators.json` is generated from\n * deploy/manifest.json by `yichus deploy --emit-calculators`, so the manifest\n * row that deploys a calculator is also its catalog entry and the two cannot\n * drift.\n */\nimport { z } from 'zod';\nimport catalog from './calculators.json' with { type: 'json' };\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 CalculatorEntry = z.strictObject({\n slug: z.string(),\n title: z.string(),\n description: z.string(),\n path: z.string(),\n});\n\nconst DEFAULT_BASE_URL = 'https://yich.us';\n\nexport const CALCULATORS: readonly Calculator[] = z.array(CalculatorEntry).parse(catalog);\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","[\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\": \"rainwater-history\",\n \"title\": \"Rainwater through a dry spell\",\n \"description\": \"Run NOAA weather observations, or your own daily rainfall CSV, through an inspectable daily tank model. Change capacity and demand, inspect captured data, and share a saved report.\",\n \"path\": \"demos/simulation/rainwater-history.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]\n","import { spawn } from 'node:child_process';\n\n/** What happened when the relay asked the platform opener for a URL. */\nexport interface OpenAttempt {\n command: string;\n args: string[];\n /** True once the opener process started; false when it could not be started. */\n opened: boolean;\n /** Why the opener could not be started (for example `spawn xdg-open ENOENT`). */\n error?: string;\n}\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 *\n * Resolves only once the opener has started or failed to start, so the\n * answer reports what happened. A machine with no opener (a container, a\n * headless box) answers `opened: false` instead of an unhandled `error`\n * event, which would end the relay and every tool with it.\n */\nexport function openUrlInBrowser(rawUrl: string): Promise<OpenAttempt> {\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 return new Promise((resolve) => {\n const child = spawn(command, args, { detached: true, stdio: 'ignore' });\n child.once('spawn', () => {\n child.unref();\n resolve({ command, args, opened: true });\n });\n // Node emits a failed start as an 'error' event; with no listener it\n // is thrown and ends the process.\n child.on('error', (error) => resolve({ command, args, opened: false, error: error.message }));\n });\n}\n","import {\n LocalRelayMcpServer,\n RelayBridgeServer,\n RelayRegistry,\n buildPublicToolName,\n type AggregatedTool,\n type RelayCallToolResult,\n type ResolvedInvocation,\n} from '@mcp-b/webmcp-local-relay';\n\ntype Hello = Parameters<RelayRegistry['upsertSource']>[1];\ntype InvocationQuery = Parameters<RelayRegistry['resolveInvocation']>[0];\ntype Transport = Parameters<LocalRelayMcpServer['connect']>[0];\ntype Message = Parameters<Transport['send']>[0];\ntype SendOptions = Parameters<Transport['send']>[1];\n\n/**\n * How many tab ids the registry remembers once their tabs are gone. A tab\n * that comes back while remembered (a reload) keeps its place; one that\n * returns after this many others is ordered as a new tab.\n */\nexport const REMEMBERED_TABS = 256;\n\n/**\n * Keeps the name an agent is already using when a second tab publishes the\n * same tool.\n *\n * The relay's own registry renames every copy of a tool once two tabs\n * publish it (`yichus_page_feedback` becomes `yichus_page_feedback_494e`\n * and `yichus_page_feedback_769c`), so opening a second site page broke the\n * name the agent had just called on the first. Here the tab the relay saw\n * first keeps the plain name and only later tabs keep the relay's tab-id\n * suffix. \"First\" is by tab id, which the embed keeps in the tab's\n * sessionStorage, so a reload of the first tab keeps its name too; when\n * that tab closes, the next-oldest remaining tab takes the plain name.\n *\n * Only the registry's public methods are refined: the relay still names,\n * ranks and resolves every tool, and the names agents see and call are\n * mapped onto its names in both directions.\n */\nexport class FirstTabNamingRegistry extends RelayRegistry {\n private readonly firstSeen = new Map<string, number>();\n private readonly tabByConnection = new Map<string, string>();\n private seen = 0;\n\n override upsertSource(connectionId: string, hello: Hello): void {\n super.upsertSource(connectionId, hello);\n this.tabByConnection.set(connectionId, hello.tabId);\n this.remember(hello.tabId);\n }\n\n override removeConnection(connectionId: string): void {\n super.removeConnection(connectionId);\n this.tabByConnection.delete(connectionId);\n }\n\n override listTools(): AggregatedTool[] {\n return this.named()\n .map(({ tool }) => tool)\n .sort((a, b) => a.name.localeCompare(b.name));\n }\n\n override resolveInvocation(options: InvocationQuery): ResolvedInvocation | null {\n const entry = this.named().find(({ tool }) => tool.name === options.toolName);\n if (!entry) return null;\n const resolved = super.resolveInvocation({ ...options, toolName: entry.relayName });\n return resolved && { ...resolved, publicToolName: entry.tool.name };\n }\n\n // Each relay tool with the name an agent sees: the plain name for the\n // copy published by the first-seen tab among those publishing it.\n private named(): { relayName: string; tool: AggregatedTool }[] {\n const tools = super.listTools();\n const owners = new Map<string, number>();\n for (const tool of tools) {\n for (const source of tool.sources) {\n const order = this.order(source.tabId);\n owners.set(tool.originalName, Math.min(owners.get(tool.originalName) ?? order, order));\n }\n }\n return tools.map((tool) => {\n const owned = tool.sources.some((source) => this.order(source.tabId) === owners.get(tool.originalName));\n const name = owned ? buildPublicToolName({ originalToolName: tool.originalName }) : tool.name;\n return { relayName: tool.name, tool: { ...tool, name } };\n });\n }\n\n private order(tabId: string): number {\n return this.firstSeen.get(tabId) ?? Number.POSITIVE_INFINITY;\n }\n\n private remember(tabId: string): void {\n if (this.firstSeen.has(tabId)) return;\n this.firstSeen.set(tabId, this.seen++);\n const open = new Set(this.tabByConnection.values());\n for (const tab of this.firstSeen.keys()) {\n if (this.firstSeen.size - open.size <= REMEMBERED_TABS) break;\n if (!open.has(tab)) this.firstSeen.delete(tab);\n }\n }\n}\n\nconst TOOL_LIST_CHANGED = 'notifications/tools/list_changed';\n\nconst isToolListChanged = (message: Message): boolean =>\n 'method' in message && message.method === TOOL_LIST_CHANGED && !('id' in message);\n\n/**\n * A transport that sends one `notifications/tools/list_changed` per burst.\n *\n * The relay mirrors each page tool as its own MCP tool, and the MCP SDK\n * sends a list_changed notice on every `registerTool` and every `remove()`;\n * the relay then sends one more of its own after each sync. A tab\n * publishing 12 tools therefore cost the agent 13 notices, and a reload (12\n * removals, 12 registrations) 25 or more, each of which many hosts answer\n * by re-reading the whole tool list. Here the first notice of a burst opens\n * a window of `windowMs` that the rest join; when it ends a single notice\n * goes out, after every registration in it has landed, so the tools/list\n * the agent sends in reply sees all of them. Closing drops a pending\n * notice. Every other message passes straight through.\n */\nexport class ToolListChangedCoalescer implements Transport {\n onclose?: Transport['onclose'];\n onerror?: Transport['onerror'];\n onmessage?: Transport['onmessage'];\n private pending: ReturnType<typeof setTimeout> | null = null;\n\n constructor(private readonly inner: Transport, private readonly windowMs: number) {\n inner.onmessage = (message, extra) => this.onmessage?.(message, extra);\n inner.onerror = (error) => this.onerror?.(error);\n inner.onclose = () => {\n this.drop();\n this.onclose?.();\n };\n }\n\n get sessionId(): string | undefined {\n return this.inner.sessionId;\n }\n\n setProtocolVersion(version: string): void {\n this.inner.setProtocolVersion?.(version);\n }\n\n start(): Promise<void> {\n return this.inner.start();\n }\n\n async send(message: Message, options?: SendOptions): Promise<void> {\n if (!isToolListChanged(message)) return this.inner.send(message, options);\n if (this.pending) return;\n this.pending = setTimeout(() => {\n this.pending = null;\n this.inner.send(message, options).catch((error: Error) => this.onerror?.(error));\n }, this.windowMs);\n this.pending.unref?.();\n }\n\n close(): Promise<void> {\n this.drop();\n return this.inner.close();\n }\n\n private drop(): void {\n if (this.pending) clearTimeout(this.pending);\n this.pending = null;\n }\n}\n\n/** The relay's MCP server, with list_changed notices coalesced on any transport it connects to. */\nexport class CoalescingRelayServer extends LocalRelayMcpServer {\n constructor(\n options: ConstructorParameters<typeof LocalRelayMcpServer>[0],\n private readonly windowMs: number,\n ) {\n super(options);\n }\n\n override connect(transport: Transport): Promise<void> {\n return super.connect(new ToolListChangedCoalescer(transport, this.windowMs));\n }\n}\n\n/**\n * A bridge that remembers what each page answered.\n *\n * The relay's `webmcp_call_tool` appends its whole tool catalog to a page's\n * answer by replacing the answer's content, the page's own refusals\n * included: \"Count (count): 100 is above the maximum 10\" arrived followed by\n * a page of tool descriptions. Here the content the page sent is kept,\n * keyed by the answer object the relay goes on to extend, so a refusal can\n * be handed to the agent exactly as the page wrote it\n * (`answerRefusalsAsThePageWroteThem` in server.ts).\n */\nexport class PageAnswerBridge extends RelayBridgeServer {\n private readonly pageContent = new WeakMap<object, RelayCallToolResult['content']>();\n\n override async invokeTool(...call: Parameters<RelayBridgeServer['invokeTool']>): Promise<RelayCallToolResult> {\n const result = await super.invokeTool(...call);\n this.pageContent.set(result, result.content);\n return result;\n }\n\n /** The content a page answered with, for an answer this bridge carried. */\n pageAnswer(result: object): RelayCallToolResult['content'] | undefined {\n return this.pageContent.get(result);\n }\n}\n","import {mkdir, writeFile} from 'node:fs/promises';\nimport {isAbsolute, join} from 'node:path';\nimport {z} from 'zod';\n\nexport const saveCalculatorInput = z.object({\n exportToolName: z.string().regex(/^yichus_example_export(?:_[a-zA-Z0-9_-]+)?$/).describe('Public export tool name from webmcp_list_tools, matched to the intended tab.'),\n exampleId: z.string().min(1),\n expectedRevision: z.number().int().nonnegative(),\n outputDirectory: z.string().refine(isAbsolute, 'Use an absolute output directory.').describe('A new directory under an existing parent. Existing directories are refused; files are never overwritten.'),\n});\n\nconst chunkSchema = z.object({\n exampleId: z.string(), revision: z.number().int(), exportId: z.string().min(1), assetBaseUrl: z.url(), selfContained: z.boolean().default(false),\n sources: z.array(z.object({fileName:z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*\\.bosatsu$/), source:z.string().max(1000000)})).min(1).max(16),\n html: z.object({fileName:z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*\\.html$/), content:z.string().min(1).max(32768),\n offset:z.number().int().nonnegative(), totalLength:z.number().int().positive().max(32*1024*1024), nextOffset:z.number().int().nonnegative().nullable()}),\n});\ntype Chunk = z.infer<typeof chunkSchema>;\ntype Invoke = (name:string, args:Record<string,unknown>) => Promise<unknown>;\n\n/** Transfer bytes through the relay without putting generated HTML in model context.\n * Validate the complete export before creating a directory. Chunks belong to one\n * compilation, and only its actual generated files are written (no execution).\n */\nexport async function saveCalculator(rawArgs:unknown, invoke:Invoke) {\n const args = saveCalculatorInput.parse(rawArgs);\n const content: string[] = [];\n let first: Chunk | undefined;\n let offset = 0;\n for (;;) {\n const response = z.object({isError:z.boolean().optional(),structuredContent:z.unknown().optional()}).parse(await invoke(args.exportToolName, {\n exampleId:args.exampleId, expectedRevision:args.expectedRevision, offset,\n ...(first ? {expectedExportId:first.exportId} : {}),\n }));\n if (response.isError) throw new Error('Browser export failed. Read the current example feedback before retrying.');\n const chunk = chunkSchema.parse(response.structuredContent);\n if (chunk.exampleId !== args.exampleId || chunk.revision !== args.expectedRevision || chunk.html.offset !== offset) throw new Error('Export does not match the requested example, revision, or offset.');\n if (first && (chunk.exportId !== first.exportId || chunk.assetBaseUrl !== first.assetBaseUrl || chunk.selfContained !== first.selfContained || chunk.html.fileName !== first.html.fileName || chunk.html.totalLength !== first.html.totalLength || JSON.stringify(chunk.sources) !== JSON.stringify(first.sources))) throw new Error('Export changed during transfer. Nothing was saved; start again.');\n first ??= chunk;\n const end = offset + chunk.html.content.length;\n if (end > chunk.html.totalLength || (chunk.html.nextOffset === null ? end !== chunk.html.totalLength : chunk.html.nextOffset !== end || end >= chunk.html.totalLength)) throw new Error('Incomplete or inconsistent export chunk. Nothing was saved.');\n content.push(chunk.html.content);\n if (chunk.html.nextOffset === null) break;\n offset = chunk.html.nextOffset;\n }\n const files = [...first.sources.map(s => ({fileName:s.fileName,content:s.source})),{fileName:first.html.fileName,content:content.join('')}];\n if (new Set(files.map(f => f.fileName.toLowerCase())).size !== files.length) throw new Error('Export file names must be unique.');\n await mkdir(args.outputDirectory);\n for (const file of files) await writeFile(join(args.outputDirectory,file.fileName),file.content,{encoding:'utf8',flag:'wx'});\n return {ok:true,directory:args.outputDirectory,revision:first.revision,exportId:first.exportId,...(first.selfContained ? {} : {assetBaseUrl:first.assetBaseUrl}),\n files:files.map(f => ({fileName:f.fileName,path:join(args.outputDirectory,f.fileName),bytes:Buffer.byteLength(f.content)})),\n htmlPath:join(args.outputDirectory,first.html.fileName),\n note:first.selfContained\n ? 'The HTML starts from configured defaults and carries its own scripts and styles, so it can be hosted anywhere and calls no Yichus server. Linking an input to a formula loads the formula compiler from jsDelivr on first use. No program was executed.'\n : 'The HTML starts from configured defaults and loads supporting assets from the site. It requires network access. No program was executed.',\n };\n}\n"],"mappings":"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","names":["z","z","z"]}
1
+ {"version":3,"sources":["../src/server.ts","../src/calculators.ts","../src/calculators.json","../src/openUrl.ts","../src/relay.ts","../src/saveCalculator.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, type OpenAttempt } from './openUrl.js';\nimport { CoalescingRelayServer, FirstTabNamingRegistry, PageAnswerBridge } from './relay.js';\nimport {saveCalculator, saveCalculatorInput} from './saveCalculator.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.\nexport const PACKAGE_VERSION = '0.1.3';\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 Yichus calculator discovery, opening, and saving tools\n * 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 bridge = new PageAnswerBridge({\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 // Cold browser compilation can outlast the relay's 25-second default.\n // Keep a deadline so an unresponsive tab still fails explicitly.\n invokeTimeoutMs: 120000,\n }, new FirstTabNamingRegistry());\n const server = new CoalescingRelayServer({\n serverName: PACKAGE_NAME,\n serverVersion: PACKAGE_VERSION,\n bridge,\n }, TOOL_LIST_CHANGED_WINDOW_MS);\n\n registerYichusTools(server, { baseUrl, disableOpener: options.disableOpener ?? false });\n answerRefusalsAsThePageWroteThem(server, bridge);\n return server;\n}\n\n/**\n * What an agent hears when it connects: what the open pages offer, and\n * where to go instead to build the person's own app.\n */\nexport const relayInstructions = (baseUrl: string) => `Yichus in the browser: the tools here come from ${new URL(baseUrl).host} pages open in the person's browser, such as a data app, the playground or the workbench. yichus_list_calculators and yichus_open_calculator open one, webmcp_list_tools lists what the open tabs offer, and each page's tools say what they do. Use them to explore a page with the person and explain what it shows.\n\nTo build the person's own app with them, use the Yichus studio instead. It is its own MCP server, run in their project folder (npx -y yichus@0.3.4 studio). It asks the person the product questions, checks the program after every change, and keeps a page open where you can point at what you mean while you talk.`;\n\n/**\n * How long one tools/list_changed notice waits for the rest of a tab's\n * registration before it goes out.\n */\nexport const TOOL_LIST_CHANGED_WINDOW_MS = 100;\n\n/**\n * Where a tool sits beside the relay's own. `@mcp-b/webmcp-local-relay`\n * has no public way to add one (its `registerStaticTool` is private), so\n * the two fields that method writes are read here, from the exactly pinned\n * version, and checked when the server is made: a release that renames\n * them fails construction instead of an agent's call.\n */\ninterface StaticToolHost {\n mcpServer: {\n server: { _instructions?: string };\n _registeredTools: { webmcp_call_tool: { handler: RelayToolHandler } };\n registerTool: (\n name: string,\n config: Record<string, unknown>,\n handler: (args: never) => Promise<unknown> | unknown\n ) => unknown;\n };\n staticToolData: Map<string, { name: string; description?: string; inputSchema: object; annotations?: object }>;\n}\n\ntype RelayToolHandler = (...call: unknown[]) => Promise<unknown>;\nconst isFunction = (field: unknown) => typeof field === 'function';\n\nconst StaticToolFields = z.object({\n mcpServer: z.object({\n // The MCP SDK's Server keeps the handshake instructions here; the relay\n // takes none of its own. server.test reads them back from initialize, so\n // a release that moves them fails there.\n server: z.custom(value => typeof value === 'object' && value !== null && '_instructions' in value),\n registerTool: z.custom(isFunction),\n // The MCP SDK's record of each registered tool; the relay's\n // webmcp_call_tool is wrapped there (answerRefusalsAsThePageWroteThem).\n _registeredTools: z.object({ webmcp_call_tool: z.object({ handler: z.custom(isFunction) }) }),\n }),\n staticToolData: z.instanceof(Map),\n});\nconst StaticToolHost = z.custom<StaticToolHost>(\n (value) => StaticToolFields.safeParse(value).success,\n { error: '@mcp-b/webmcp-local-relay no longer has mcpServer.registerTool, a registered webmcp_call_tool handler, an SDK server with instructions and staticToolData' },\n);\n\nfunction registerYichusTools(\n server: LocalRelayMcpServer,\n ctx: { baseUrl: string; disableOpener: boolean }\n): void {\n const host = StaticToolHost.parse(server);\n host.mcpServer.server._instructions = relayInstructions(ctx.baseUrl);\n\n registerStatic(host, {\n name:'yichus_save_calculator',\n description:'Save a browser-compiled example and its Bosatsu sources to a NEW local directory. Pass the public yichus_example_export tool name for the intended tab and its current source revision. Transfers bounded chunks internally and returns file paths, not generated HTML. Does not execute the program. Requires the browser tab to stay open.',\n annotations:{readOnlyHint:false,destructiveHint:false},\n input:saveCalculatorInput,\n handler:async args => {\n try {\n const payload = await saveCalculator(args,(name,params) => server.bridge.invokeTool(name,params));\n return {content:[{type:'text',text:JSON.stringify(payload)}],structuredContent:payload};\n } catch (error) {\n return {isError:true,content:[{type:'text',text:String(error)}]};\n }\n },\n });\n\n registerStatic(host, {\n name: 'yichus_list_calculators',\n description:\n 'List the curated Yichus data app 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 input: z.object({}),\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(host, {\n name: 'yichus_open_calculator',\n description:\n \"Open a curated Yichus data app (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 data apps; yichus_playground_* for the playground). Use yichus_list_calculators to discover slugs.\",\n annotations: { openWorldHint: true },\n input: z.object({\n slug: z.string().describe(\n 'Data app slug from yichus_list_calculators (for example \"loan\", \"investment\", \"tax\", \"playground\").'\n ),\n }),\n handler: async (args) => {\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 data app slug \"${args.slug}\". Known slugs: ${known}.`,\n },\n ],\n isError: true,\n };\n }\n const url = withWebMcpFlag(calculatorUrl(calc, ctx.baseUrl));\n const attempt: OpenAttempt | null = ctx.disableOpener ? null : await openUrlInBrowser(url);\n const payload = {\n calculator: {\n slug: calc.slug,\n title: calc.title,\n description: calc.description,\n },\n url,\n opened: attempt?.opened ?? false,\n opener: attempt && { command: attempt.command, args: attempt.args },\n ...(attempt?.error === undefined ? {} : { openerError: attempt.error }),\n };\n return {\n content: [{ type: 'text', text: openAnswer(calc, url, attempt) }],\n structuredContent: payload,\n };\n },\n });\n}\n\n/** The sentence an agent relays: what happened, and the link to open by hand when no browser was opened. */\nfunction openAnswer(calc: Calculator, url: string, attempt: OpenAttempt | null): string {\n if (attempt === null) return `Would open ${calc.title} at ${url} (browser opener disabled).`;\n if (attempt.opened) return `Opening ${calc.title} at ${url}.`;\n return `Could not open a browser on this machine (${attempt.error}). ` +\n `Open this link in a browser yourself to load ${calc.title}: ${url}`;\n}\n\n/**\n * Hands a page's refusal to the agent as the page wrote it.\n *\n * The relay's `webmcp_call_tool` adds its tool catalog to every page\n * answer. On a result that worked that is a reminder of what else can be\n * called; on a refusal (\"Loan Principal ($) (principal): 5000000 is above\n * the maximum 1000000 ...\") it buried the one sentence the agent had to\n * relay. The bridge kept the content each page sent, keyed by the answer\n * the relay extends; a refusal is answered with that content alone. The\n * relay's own errors (an unknown tool name, a page that never answered) are\n * not page answers and keep the catalog, which lists the names to use.\n */\nfunction answerRefusalsAsThePageWroteThem(server: LocalRelayMcpServer, bridge: PageAnswerBridge): void {\n const callTool = StaticToolHost.parse(server).mcpServer._registeredTools.webmcp_call_tool;\n const relayHandler = callTool.handler;\n callTool.handler = async (...call) => {\n const result = (await relayHandler(...call)) as { isError?: boolean };\n const page = bridge.pageAnswer(result);\n return result.isError === true && page !== undefined ? { ...result, content: page } : result;\n };\n}\n\ninterface StaticToolSpec<Input extends z.ZodObject> {\n name: string;\n description: string;\n annotations: Record<string, unknown>;\n input: Input;\n handler: (args: z.infer<Input>) => Promise<unknown> | unknown;\n}\n\n// The MCP SDK validates calls against the input's shape; agents read the\n// JSON Schema Zod derives from the same input.\nfunction registerStatic<Input extends z.ZodObject>(host: StaticToolHost, spec: StaticToolSpec<Input>): void {\n const { name, description, annotations, input, handler } = spec;\n host.mcpServer.registerTool(name, { description, inputSchema: input.shape, annotations }, handler);\n host.staticToolData.set(name, {\n name,\n description,\n inputSchema: z.toJSONSchema(input, { unrepresentable: 'any' }),\n annotations,\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};\n","/**\n * Curated catalogue of Yichus simulation demos.\n *\n * The entries are data: `calculators.json` is generated from\n * deploy/manifest.json by `yichus deploy --emit-calculators`, so the manifest\n * row that deploys a calculator is also its catalog entry and the two cannot\n * drift.\n */\nimport { z } from 'zod';\nimport catalog from './calculators.json' with { type: 'json' };\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 CalculatorEntry = z.strictObject({\n slug: z.string(),\n title: z.string(),\n description: z.string(),\n path: z.string(),\n});\n\nconst DEFAULT_BASE_URL = 'https://yich.us';\n\nexport const CALCULATORS: readonly Calculator[] = z.array(CalculatorEntry).parse(catalog);\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","[\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\": \"rainwater-history\",\n \"title\": \"Rainwater through a dry spell\",\n \"description\": \"Run NOAA weather observations, or your own daily rainfall CSV, through an inspectable daily tank model. Change capacity and demand, inspect captured data, and share a saved report.\",\n \"path\": \"demos/simulation/rainwater-history.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]\n","import { spawn } from 'node:child_process';\n\n/** What happened when the relay asked the platform opener for a URL. */\nexport interface OpenAttempt {\n command: string;\n args: string[];\n /** True once the opener process started; false when it could not be started. */\n opened: boolean;\n /** Why the opener could not be started (for example `spawn xdg-open ENOENT`). */\n error?: string;\n}\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 *\n * Resolves only once the opener has started or failed to start, so the\n * answer reports what happened. A machine with no opener (a container, a\n * headless box) answers `opened: false` instead of an unhandled `error`\n * event, which would end the relay and every tool with it.\n */\nexport function openUrlInBrowser(rawUrl: string): Promise<OpenAttempt> {\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 return new Promise((resolve) => {\n const child = spawn(command, args, { detached: true, stdio: 'ignore' });\n child.once('spawn', () => {\n child.unref();\n resolve({ command, args, opened: true });\n });\n // Node emits a failed start as an 'error' event; with no listener it\n // is thrown and ends the process.\n child.on('error', (error) => resolve({ command, args, opened: false, error: error.message }));\n });\n}\n","import {\n LocalRelayMcpServer,\n RelayBridgeServer,\n RelayRegistry,\n buildPublicToolName,\n type AggregatedTool,\n type RelayCallToolResult,\n type ResolvedInvocation,\n} from '@mcp-b/webmcp-local-relay';\n\ntype Hello = Parameters<RelayRegistry['upsertSource']>[1];\ntype InvocationQuery = Parameters<RelayRegistry['resolveInvocation']>[0];\ntype Transport = Parameters<LocalRelayMcpServer['connect']>[0];\ntype Message = Parameters<Transport['send']>[0];\ntype SendOptions = Parameters<Transport['send']>[1];\n\n/**\n * How many tab ids the registry remembers once their tabs are gone. A tab\n * that comes back while remembered (a reload) keeps its place; one that\n * returns after this many others is ordered as a new tab.\n */\nexport const REMEMBERED_TABS = 256;\n\n/**\n * Keeps the name an agent is already using when a second tab publishes the\n * same tool.\n *\n * The relay's own registry renames every copy of a tool once two tabs\n * publish it (`yichus_page_feedback` becomes `yichus_page_feedback_494e`\n * and `yichus_page_feedback_769c`), so opening a second site page broke the\n * name the agent had just called on the first. Here the tab the relay saw\n * first keeps the plain name and only later tabs keep the relay's tab-id\n * suffix. \"First\" is by tab id, which the embed keeps in the tab's\n * sessionStorage, so a reload of the first tab keeps its name too; when\n * that tab closes, the next-oldest remaining tab takes the plain name.\n *\n * Only the registry's public methods are refined: the relay still names,\n * ranks and resolves every tool, and the names agents see and call are\n * mapped onto its names in both directions.\n */\nexport class FirstTabNamingRegistry extends RelayRegistry {\n private readonly firstSeen = new Map<string, number>();\n private readonly tabByConnection = new Map<string, string>();\n private seen = 0;\n\n override upsertSource(connectionId: string, hello: Hello): void {\n super.upsertSource(connectionId, hello);\n this.tabByConnection.set(connectionId, hello.tabId);\n this.remember(hello.tabId);\n }\n\n override removeConnection(connectionId: string): void {\n super.removeConnection(connectionId);\n this.tabByConnection.delete(connectionId);\n }\n\n override listTools(): AggregatedTool[] {\n return this.named()\n .map(({ tool }) => tool)\n .sort((a, b) => a.name.localeCompare(b.name));\n }\n\n override resolveInvocation(options: InvocationQuery): ResolvedInvocation | null {\n const entry = this.named().find(({ tool }) => tool.name === options.toolName);\n if (!entry) return null;\n const resolved = super.resolveInvocation({ ...options, toolName: entry.relayName });\n return resolved && { ...resolved, publicToolName: entry.tool.name };\n }\n\n // Each relay tool with the name an agent sees: the plain name for the\n // copy published by the first-seen tab among those publishing it.\n private named(): { relayName: string; tool: AggregatedTool }[] {\n const tools = super.listTools();\n const owners = new Map<string, number>();\n for (const tool of tools) {\n for (const source of tool.sources) {\n const order = this.order(source.tabId);\n owners.set(tool.originalName, Math.min(owners.get(tool.originalName) ?? order, order));\n }\n }\n return tools.map((tool) => {\n const owned = tool.sources.some((source) => this.order(source.tabId) === owners.get(tool.originalName));\n const name = owned ? buildPublicToolName({ originalToolName: tool.originalName }) : tool.name;\n return { relayName: tool.name, tool: { ...tool, name } };\n });\n }\n\n private order(tabId: string): number {\n return this.firstSeen.get(tabId) ?? Number.POSITIVE_INFINITY;\n }\n\n private remember(tabId: string): void {\n if (this.firstSeen.has(tabId)) return;\n this.firstSeen.set(tabId, this.seen++);\n const open = new Set(this.tabByConnection.values());\n for (const tab of this.firstSeen.keys()) {\n if (this.firstSeen.size - open.size <= REMEMBERED_TABS) break;\n if (!open.has(tab)) this.firstSeen.delete(tab);\n }\n }\n}\n\nconst TOOL_LIST_CHANGED = 'notifications/tools/list_changed';\n\nconst isToolListChanged = (message: Message): boolean =>\n 'method' in message && message.method === TOOL_LIST_CHANGED && !('id' in message);\n\n/**\n * A transport that sends one `notifications/tools/list_changed` per burst.\n *\n * The relay mirrors each page tool as its own MCP tool, and the MCP SDK\n * sends a list_changed notice on every `registerTool` and every `remove()`;\n * the relay then sends one more of its own after each sync. A tab\n * publishing 12 tools therefore cost the agent 13 notices, and a reload (12\n * removals, 12 registrations) 25 or more, each of which many hosts answer\n * by re-reading the whole tool list. Here the first notice of a burst opens\n * a window of `windowMs` that the rest join; when it ends a single notice\n * goes out, after every registration in it has landed, so the tools/list\n * the agent sends in reply sees all of them. Closing drops a pending\n * notice. Every other message passes straight through.\n */\nexport class ToolListChangedCoalescer implements Transport {\n onclose?: Transport['onclose'];\n onerror?: Transport['onerror'];\n onmessage?: Transport['onmessage'];\n private pending: ReturnType<typeof setTimeout> | null = null;\n\n constructor(private readonly inner: Transport, private readonly windowMs: number) {\n inner.onmessage = (message, extra) => this.onmessage?.(message, extra);\n inner.onerror = (error) => this.onerror?.(error);\n inner.onclose = () => {\n this.drop();\n this.onclose?.();\n };\n }\n\n get sessionId(): string | undefined {\n return this.inner.sessionId;\n }\n\n setProtocolVersion(version: string): void {\n this.inner.setProtocolVersion?.(version);\n }\n\n start(): Promise<void> {\n return this.inner.start();\n }\n\n async send(message: Message, options?: SendOptions): Promise<void> {\n if (!isToolListChanged(message)) return this.inner.send(message, options);\n if (this.pending) return;\n this.pending = setTimeout(() => {\n this.pending = null;\n this.inner.send(message, options).catch((error: Error) => this.onerror?.(error));\n }, this.windowMs);\n this.pending.unref?.();\n }\n\n close(): Promise<void> {\n this.drop();\n return this.inner.close();\n }\n\n private drop(): void {\n if (this.pending) clearTimeout(this.pending);\n this.pending = null;\n }\n}\n\n/** The relay's MCP server, with list_changed notices coalesced on any transport it connects to. */\nexport class CoalescingRelayServer extends LocalRelayMcpServer {\n constructor(\n options: ConstructorParameters<typeof LocalRelayMcpServer>[0],\n private readonly windowMs: number,\n ) {\n super(options);\n }\n\n override connect(transport: Transport): Promise<void> {\n return super.connect(new ToolListChangedCoalescer(transport, this.windowMs));\n }\n}\n\n/**\n * A bridge that remembers what each page answered.\n *\n * The relay's `webmcp_call_tool` appends its whole tool catalog to a page's\n * answer by replacing the answer's content, the page's own refusals\n * included: \"Count (count): 100 is above the maximum 10\" arrived followed by\n * a page of tool descriptions. Here the content the page sent is kept,\n * keyed by the answer object the relay goes on to extend, so a refusal can\n * be handed to the agent exactly as the page wrote it\n * (`answerRefusalsAsThePageWroteThem` in server.ts).\n */\nexport class PageAnswerBridge extends RelayBridgeServer {\n private readonly pageContent = new WeakMap<object, RelayCallToolResult['content']>();\n\n override async invokeTool(...call: Parameters<RelayBridgeServer['invokeTool']>): Promise<RelayCallToolResult> {\n const result = await super.invokeTool(...call);\n this.pageContent.set(result, result.content);\n return result;\n }\n\n /** The content a page answered with, for an answer this bridge carried. */\n pageAnswer(result: object): RelayCallToolResult['content'] | undefined {\n return this.pageContent.get(result);\n }\n}\n","import {mkdir, writeFile} from 'node:fs/promises';\nimport {isAbsolute, join} from 'node:path';\nimport {z} from 'zod';\n\nexport const saveCalculatorInput = z.object({\n exportToolName: z.string().regex(/^yichus_example_export(?:_[a-zA-Z0-9_-]+)?$/).describe('Public export tool name from webmcp_list_tools, matched to the intended tab.'),\n exampleId: z.string().min(1),\n expectedRevision: z.number().int().nonnegative(),\n outputDirectory: z.string().refine(isAbsolute, 'Use an absolute output directory.').describe('A new directory under an existing parent. Existing directories are refused; files are never overwritten.'),\n});\n\nconst chunkSchema = z.object({\n exampleId: z.string(), revision: z.number().int(), exportId: z.string().min(1), assetBaseUrl: z.url(), selfContained: z.boolean().default(false),\n sources: z.array(z.object({fileName:z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*\\.bosatsu$/), source:z.string().max(1000000)})).min(1).max(16),\n html: z.object({fileName:z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*\\.html$/), content:z.string().min(1).max(32768),\n offset:z.number().int().nonnegative(), totalLength:z.number().int().positive().max(32*1024*1024), nextOffset:z.number().int().nonnegative().nullable()}),\n});\ntype Chunk = z.infer<typeof chunkSchema>;\ntype Invoke = (name:string, args:Record<string,unknown>) => Promise<unknown>;\n\n/** Transfer bytes through the relay without putting generated HTML in model context.\n * Validate the complete export before creating a directory. Chunks belong to one\n * compilation, and only its actual generated files are written (no execution).\n */\nexport async function saveCalculator(rawArgs:unknown, invoke:Invoke) {\n const args = saveCalculatorInput.parse(rawArgs);\n const content: string[] = [];\n let first: Chunk | undefined;\n let offset = 0;\n for (;;) {\n const response = z.object({isError:z.boolean().optional(),structuredContent:z.unknown().optional()}).parse(await invoke(args.exportToolName, {\n exampleId:args.exampleId, expectedRevision:args.expectedRevision, offset,\n ...(first ? {expectedExportId:first.exportId} : {}),\n }));\n if (response.isError) throw new Error('Browser export failed. Read the current example feedback before retrying.');\n const chunk = chunkSchema.parse(response.structuredContent);\n if (chunk.exampleId !== args.exampleId || chunk.revision !== args.expectedRevision || chunk.html.offset !== offset) throw new Error('Export does not match the requested example, revision, or offset.');\n if (first && (chunk.exportId !== first.exportId || chunk.assetBaseUrl !== first.assetBaseUrl || chunk.selfContained !== first.selfContained || chunk.html.fileName !== first.html.fileName || chunk.html.totalLength !== first.html.totalLength || JSON.stringify(chunk.sources) !== JSON.stringify(first.sources))) throw new Error('Export changed during transfer. Nothing was saved; start again.');\n first ??= chunk;\n const end = offset + chunk.html.content.length;\n if (end > chunk.html.totalLength || (chunk.html.nextOffset === null ? end !== chunk.html.totalLength : chunk.html.nextOffset !== end || end >= chunk.html.totalLength)) throw new Error('Incomplete or inconsistent export chunk. Nothing was saved.');\n content.push(chunk.html.content);\n if (chunk.html.nextOffset === null) break;\n offset = chunk.html.nextOffset;\n }\n const files = [...first.sources.map(s => ({fileName:s.fileName,content:s.source})),{fileName:first.html.fileName,content:content.join('')}];\n if (new Set(files.map(f => f.fileName.toLowerCase())).size !== files.length) throw new Error('Export file names must be unique.');\n await mkdir(args.outputDirectory);\n for (const file of files) await writeFile(join(args.outputDirectory,file.fileName),file.content,{encoding:'utf8',flag:'wx'});\n return {ok:true,directory:args.outputDirectory,revision:first.revision,exportId:first.exportId,...(first.selfContained ? {} : {assetBaseUrl:first.assetBaseUrl}),\n files:files.map(f => ({fileName:f.fileName,path:join(args.outputDirectory,f.fileName),bytes:Buffer.byteLength(f.content)})),\n htmlPath:join(args.outputDirectory,first.html.fileName),\n note:first.selfContained\n ? 'The HTML starts from configured defaults and carries its own scripts and styles, so it can be hosted anywhere and calls no Yichus server. Linking an input to a formula loads the formula compiler from jsDelivr on first use. No program was executed.'\n : 'The HTML starts from configured defaults and loads supporting assets from the site. It requires network access. No program was executed.',\n };\n}\n"],"mappings":"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","names":["z","z","z"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yichus/mcp",
3
- "version": "0.1.2",
4
- "description": "MCP server that exposes the Yichus calculators and playground over WebMCP",
3
+ "version": "0.1.3",
4
+ "description": "MCP server that exposes the Yichus data apps and playground over WebMCP",
5
5
  "license": "MIT",
6
6
  "homepage": "https://yich.us",
7
7
  "repository": {