@yichus/mcp 0.1.1 → 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 +24 -40
- package/dist/cli.js +69 -49
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +0 -7
- package/dist/index.js +63 -42
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,51 +1,31 @@
|
|
|
1
1
|
# @yichus/mcp
|
|
2
2
|
|
|
3
|
-
MCP server that exposes the [Yichus](https://yich.us)
|
|
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
|
|
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
|
|
|
11
11
|
## Install and connect
|
|
12
12
|
|
|
13
|
-
Node 20 or newer; the relay does not need Java.
|
|
14
|
-
|
|
13
|
+
Node 20 or newer; the relay does not need Java. Configure your MCP client to
|
|
14
|
+
launch it as a stdio server:
|
|
15
15
|
|
|
16
16
|
```json
|
|
17
17
|
{
|
|
18
18
|
"mcpServers": {
|
|
19
19
|
"yichus": {
|
|
20
20
|
"command": "npx",
|
|
21
|
-
"args": ["-y", "@yichus/mcp@0.1.
|
|
21
|
+
"args": ["-y", "@yichus/mcp@0.1.3"]
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Version 0.1.0
|
|
28
|
-
|
|
29
|
-
fixes. Until 0.1.1 is published, build the current relay from a checkout
|
|
30
|
-
with Node 20+ and pnpm:
|
|
31
|
-
|
|
32
|
-
```sh
|
|
33
|
-
pnpm install
|
|
34
|
-
pnpm run yichus-mcp:build
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
and launch it by your checkout's absolute path:
|
|
38
|
-
|
|
39
|
-
```json
|
|
40
|
-
{
|
|
41
|
-
"mcpServers": {
|
|
42
|
-
"yichus": {
|
|
43
|
-
"command": "node",
|
|
44
|
-
"args": ["/absolute/path/to/yichus/packages/yichus-mcp/dist/cli.js"]
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
```
|
|
27
|
+
Version 0.1.0 has no data app saving tool (`yichus_save_calculator`) and
|
|
28
|
+
lacks later relay fixes.
|
|
49
29
|
|
|
50
30
|
Open the desired page with `?webmcp=1`. Keep it open, call
|
|
51
31
|
`webmcp_list_sources`, and identify that tab. Discover its schemas and public
|
|
@@ -60,17 +40,21 @@ For a local site, set `--base-url` to the origin you opened.
|
|
|
60
40
|
A browser host with native WebMCP support can call page tools without this
|
|
61
41
|
relay. See [connection instructions](https://yich.us/reference/agent.html).
|
|
62
42
|
|
|
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.4
|
|
45
|
+
studio`, a separate MCP server in the project folder), not this relay's.
|
|
46
|
+
|
|
63
47
|
## Choose the tools for the page
|
|
64
48
|
|
|
65
49
|
- **An editable Code & run example** on the website: call
|
|
66
50
|
`yichus_example_guide`, `yichus_examples_list`, and `yichus_example_read`.
|
|
67
51
|
Use `yichus_example_update` with exact file names and the read revision;
|
|
68
52
|
`yichus_example_run` compiles/runs or checks the program. Inspect
|
|
69
|
-
`yichus_example_feedback`. Once a
|
|
53
|
+
`yichus_example_feedback`. Once a data app preview is ready,
|
|
70
54
|
`yichus_example_preview` changes its visible controls or clicks Why.
|
|
71
55
|
Pass the current source revision and preview id, so an old call cannot
|
|
72
56
|
act on a replacement preview. Read-only demo drawers report that limit.
|
|
73
|
-
- **
|
|
57
|
+
- **Data app editor**, `editor.html`: `yichus_playground_get_sources`,
|
|
74
58
|
`yichus_playground_update_sources`, `yichus_playground_compile`,
|
|
75
59
|
`yichus_playground_get_output`, and `yichus_playground_reset`. Read sources
|
|
76
60
|
first and pass their revision as `expectedRevision` when updating. Output
|
|
@@ -81,9 +65,9 @@ relay. See [connection instructions](https://yich.us/reference/agent.html).
|
|
|
81
65
|
and `api_why` update the visible diagrams/evaluator. The `yichus_app_*`
|
|
82
66
|
tools open a generated app, exercise routes, inspect rows, and test
|
|
83
67
|
configured crash/restart behavior. The page links their reference.
|
|
84
|
-
- **Standalone
|
|
68
|
+
- **Standalone data app**: `yichus_list_inputs`, `yichus_set_inputs`,
|
|
85
69
|
`yichus_list_outputs`, `yichus_explain_output`. These operate the running
|
|
86
|
-
|
|
70
|
+
data app; use an editable source drawer or the editor to change code.
|
|
87
71
|
An explanation is plain data: the formula in the page's labels, the same
|
|
88
72
|
formula with current values, the inputs and outputs it reads, and the
|
|
89
73
|
unlabeled steps in between.
|
|
@@ -97,7 +81,7 @@ relay. See [connection instructions](https://yich.us/reference/agent.html).
|
|
|
97
81
|
|
|
98
82
|
The static `yichus_list_calculators` tool lists curated demo URLs;
|
|
99
83
|
`yichus_open_calculator` opens a chosen slug with the relay flag. The
|
|
100
|
-
`playground` slug opens the standalone
|
|
84
|
+
`playground` slug opens the standalone data app editor. On a machine with
|
|
101
85
|
no browser opener (a container or headless box) it answers `opened: false`
|
|
102
86
|
with the link for a person to open by hand.
|
|
103
87
|
|
|
@@ -108,7 +92,7 @@ with the link for a person to open by hand.
|
|
|
108
92
|
If that revision is stale, read the user's newer edit before proceeding.
|
|
109
93
|
3. Compile and fix diagnostics. For an API example, rerun the appropriate
|
|
110
94
|
structural check and a behavior check.
|
|
111
|
-
4. Inspect feedback from the current result. Change a
|
|
95
|
+
4. Inspect feedback from the current result. Change a data app input and
|
|
112
96
|
inspect Why. Report tested inputs, actual results, and remaining limits.
|
|
113
97
|
5. Save the edited sources before leaving the page. A successful compilation
|
|
114
98
|
and a few executions do not establish that every model assumption is sound.
|
|
@@ -119,10 +103,10 @@ The user accepts the ghost text with Tab or dismisses it with Escape.
|
|
|
119
103
|
`yichus_autocomplete_list_editors`, `yichus_autocomplete_get_context`, and
|
|
120
104
|
`yichus_autocomplete_dismiss` support inspection and cancellation.
|
|
121
105
|
|
|
122
|
-
## Save a browser-compiled
|
|
106
|
+
## Save a browser-compiled data app
|
|
123
107
|
|
|
124
108
|
The [agent QuickStart](https://yich.us/start/quickstart.html) walks through a
|
|
125
|
-
|
|
109
|
+
data app change, compilation, Why feedback, and local execution.
|
|
126
110
|
|
|
127
111
|
Use `webmcp_list_sources` to identify that page, then `webmcp_list_tools`
|
|
128
112
|
to find its tools by their `sources`. Call the returned public `name`:
|
|
@@ -131,7 +115,7 @@ the plain name and later tabs' copies gain a tab-id suffix. The
|
|
|
131
115
|
`webmcp_call_tool` arguments are `name` and `arguments`; it does not accept
|
|
132
116
|
a `sourceId` parameter.
|
|
133
117
|
|
|
134
|
-
After testing the
|
|
118
|
+
After testing the data app, call the relay's `yichus_save_calculator`
|
|
135
119
|
directly with:
|
|
136
120
|
|
|
137
121
|
```json
|
|
@@ -156,7 +140,7 @@ Open the HTML or serve its directory with a static server. It starts at
|
|
|
156
140
|
the configured defaults, and supporting scripts and styles still load
|
|
157
141
|
from the exported site's URL, so internet access is required. The save
|
|
158
142
|
tool does not execute the program. In other browser hosts, use the page's
|
|
159
|
-
**Save
|
|
143
|
+
**Save data app HTML** button or implement the paged
|
|
160
144
|
`yichus_example_export` contract (`nextOffset` and `expectedExportId`).
|
|
161
145
|
|
|
162
146
|
## CLI
|
|
@@ -180,11 +164,11 @@ page hints the relay widget workspace `yichus` and relay id `yichus`, and
|
|
|
180
164
|
the widget refuses a relay whose discovery announcement differs from
|
|
181
165
|
either. Change them only for a page built to hint the same values.
|
|
182
166
|
|
|
183
|
-
For
|
|
184
|
-
|
|
167
|
+
For a self-hosted copy of the site, point both the relay's allowed origin
|
|
168
|
+
and the URL builder at its server:
|
|
185
169
|
|
|
186
170
|
```bash
|
|
187
|
-
|
|
171
|
+
npx -y @yichus/mcp@0.1.3 --base-url http://localhost:8000
|
|
188
172
|
```
|
|
189
173
|
|
|
190
174
|
## What this package does and does not do
|
package/dist/cli.js
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { parseArgs } from "util";
|
|
5
|
-
import { z as
|
|
5
|
+
import { z as z4 } from "zod";
|
|
6
6
|
|
|
7
7
|
// src/server.ts
|
|
8
|
-
import { z as
|
|
8
|
+
import { z as z3 } from "zod";
|
|
9
9
|
|
|
10
10
|
// src/calculators.ts
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
// src/calculators.json
|
|
14
|
+
var calculators_default = [
|
|
13
15
|
{
|
|
14
16
|
slug: "event-budget",
|
|
15
17
|
title: "Event budget",
|
|
@@ -25,7 +27,7 @@ var CALCULATORS = [
|
|
|
25
27
|
{
|
|
26
28
|
slug: "rainwater-history",
|
|
27
29
|
title: "Rainwater through a dry spell",
|
|
28
|
-
description: "Run NOAA weather observations through an inspectable daily tank model. Change capacity and demand, inspect captured data, and share a saved report.",
|
|
30
|
+
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.",
|
|
29
31
|
path: "demos/simulation/rainwater-history.html"
|
|
30
32
|
},
|
|
31
33
|
{
|
|
@@ -137,6 +139,16 @@ var CALCULATORS = [
|
|
|
137
139
|
path: "demos/simulation/carbon-abatement.html"
|
|
138
140
|
}
|
|
139
141
|
];
|
|
142
|
+
|
|
143
|
+
// src/calculators.ts
|
|
144
|
+
var CalculatorEntry = z.strictObject({
|
|
145
|
+
slug: z.string(),
|
|
146
|
+
title: z.string(),
|
|
147
|
+
description: z.string(),
|
|
148
|
+
path: z.string()
|
|
149
|
+
});
|
|
150
|
+
var DEFAULT_BASE_URL = "https://yich.us";
|
|
151
|
+
var CALCULATORS = z.array(CalculatorEntry).parse(calculators_default);
|
|
140
152
|
var PLAYGROUND = {
|
|
141
153
|
slug: "playground",
|
|
142
154
|
title: "Yichus Bosatsu Playground",
|
|
@@ -319,25 +331,26 @@ var PageAnswerBridge = class extends RelayBridgeServer {
|
|
|
319
331
|
// src/saveCalculator.ts
|
|
320
332
|
import { mkdir, writeFile } from "fs/promises";
|
|
321
333
|
import { isAbsolute, join } from "path";
|
|
322
|
-
import { z } from "zod";
|
|
323
|
-
var saveCalculatorInput =
|
|
324
|
-
exportToolName:
|
|
325
|
-
exampleId:
|
|
326
|
-
expectedRevision:
|
|
327
|
-
outputDirectory:
|
|
334
|
+
import { z as z2 } from "zod";
|
|
335
|
+
var saveCalculatorInput = z2.object({
|
|
336
|
+
exportToolName: z2.string().regex(/^yichus_example_export(?:_[a-zA-Z0-9_-]+)?$/).describe("Public export tool name from webmcp_list_tools, matched to the intended tab."),
|
|
337
|
+
exampleId: z2.string().min(1),
|
|
338
|
+
expectedRevision: z2.number().int().nonnegative(),
|
|
339
|
+
outputDirectory: z2.string().refine(isAbsolute, "Use an absolute output directory.").describe("A new directory under an existing parent. Existing directories are refused; files are never overwritten.")
|
|
328
340
|
});
|
|
329
|
-
var chunkSchema =
|
|
330
|
-
exampleId:
|
|
331
|
-
revision:
|
|
332
|
-
exportId:
|
|
333
|
-
assetBaseUrl:
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
+
var chunkSchema = z2.object({
|
|
342
|
+
exampleId: z2.string(),
|
|
343
|
+
revision: z2.number().int(),
|
|
344
|
+
exportId: z2.string().min(1),
|
|
345
|
+
assetBaseUrl: z2.url(),
|
|
346
|
+
selfContained: z2.boolean().default(false),
|
|
347
|
+
sources: z2.array(z2.object({ fileName: z2.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*\.bosatsu$/), source: z2.string().max(1e6) })).min(1).max(16),
|
|
348
|
+
html: z2.object({
|
|
349
|
+
fileName: z2.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*\.html$/),
|
|
350
|
+
content: z2.string().min(1).max(32768),
|
|
351
|
+
offset: z2.number().int().nonnegative(),
|
|
352
|
+
totalLength: z2.number().int().positive().max(32 * 1024 * 1024),
|
|
353
|
+
nextOffset: z2.number().int().nonnegative().nullable()
|
|
341
354
|
})
|
|
342
355
|
});
|
|
343
356
|
async function saveCalculator(rawArgs, invoke) {
|
|
@@ -346,7 +359,7 @@ async function saveCalculator(rawArgs, invoke) {
|
|
|
346
359
|
let first;
|
|
347
360
|
let offset = 0;
|
|
348
361
|
for (; ; ) {
|
|
349
|
-
const response =
|
|
362
|
+
const response = z2.object({ isError: z2.boolean().optional(), structuredContent: z2.unknown().optional() }).parse(await invoke(args.exportToolName, {
|
|
350
363
|
exampleId: args.exampleId,
|
|
351
364
|
expectedRevision: args.expectedRevision,
|
|
352
365
|
offset,
|
|
@@ -355,7 +368,7 @@ async function saveCalculator(rawArgs, invoke) {
|
|
|
355
368
|
if (response.isError) throw new Error("Browser export failed. Read the current example feedback before retrying.");
|
|
356
369
|
const chunk = chunkSchema.parse(response.structuredContent);
|
|
357
370
|
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.");
|
|
358
|
-
if (first && (chunk.exportId !== first.exportId || chunk.assetBaseUrl !== first.assetBaseUrl || 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.");
|
|
371
|
+
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.");
|
|
359
372
|
first ??= chunk;
|
|
360
373
|
const end = offset + chunk.html.content.length;
|
|
361
374
|
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.");
|
|
@@ -372,16 +385,16 @@ async function saveCalculator(rawArgs, invoke) {
|
|
|
372
385
|
directory: args.outputDirectory,
|
|
373
386
|
revision: first.revision,
|
|
374
387
|
exportId: first.exportId,
|
|
375
|
-
assetBaseUrl: first.assetBaseUrl,
|
|
388
|
+
...first.selfContained ? {} : { assetBaseUrl: first.assetBaseUrl },
|
|
376
389
|
files: files.map((f) => ({ fileName: f.fileName, path: join(args.outputDirectory, f.fileName), bytes: Buffer.byteLength(f.content) })),
|
|
377
390
|
htmlPath: join(args.outputDirectory, first.html.fileName),
|
|
378
|
-
note: "The HTML starts from configured defaults and loads supporting assets from the site. It requires network access. No program was executed."
|
|
391
|
+
note: first.selfContained ? "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." : "The HTML starts from configured defaults and loads supporting assets from the site. It requires network access. No program was executed."
|
|
379
392
|
};
|
|
380
393
|
}
|
|
381
394
|
|
|
382
395
|
// src/server.ts
|
|
383
396
|
var PACKAGE_NAME = "yichus-mcp";
|
|
384
|
-
var PACKAGE_VERSION = "0.1.
|
|
397
|
+
var PACKAGE_VERSION = "0.1.3";
|
|
385
398
|
function createYichusMcpServer(options = {}) {
|
|
386
399
|
const baseUrl = normalizeBaseUrl(options.baseUrl ?? "https://yich.us");
|
|
387
400
|
const bridgeOverrides = options.bridgeOverrides ?? {};
|
|
@@ -407,23 +420,31 @@ function createYichusMcpServer(options = {}) {
|
|
|
407
420
|
answerRefusalsAsThePageWroteThem(server, bridge);
|
|
408
421
|
return server;
|
|
409
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 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
|
+
|
|
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.`;
|
|
410
426
|
var TOOL_LIST_CHANGED_WINDOW_MS = 100;
|
|
411
427
|
var isFunction = (field) => typeof field === "function";
|
|
412
|
-
var StaticToolFields =
|
|
413
|
-
mcpServer:
|
|
414
|
-
|
|
428
|
+
var StaticToolFields = z3.object({
|
|
429
|
+
mcpServer: z3.object({
|
|
430
|
+
// The MCP SDK's Server keeps the handshake instructions here; the relay
|
|
431
|
+
// takes none of its own. server.test reads them back from initialize, so
|
|
432
|
+
// a release that moves them fails there.
|
|
433
|
+
server: z3.custom((value) => typeof value === "object" && value !== null && "_instructions" in value),
|
|
434
|
+
registerTool: z3.custom(isFunction),
|
|
415
435
|
// The MCP SDK's record of each registered tool; the relay's
|
|
416
436
|
// webmcp_call_tool is wrapped there (answerRefusalsAsThePageWroteThem).
|
|
417
|
-
_registeredTools:
|
|
437
|
+
_registeredTools: z3.object({ webmcp_call_tool: z3.object({ handler: z3.custom(isFunction) }) })
|
|
418
438
|
}),
|
|
419
|
-
staticToolData:
|
|
439
|
+
staticToolData: z3.instanceof(Map)
|
|
420
440
|
});
|
|
421
|
-
var StaticToolHost =
|
|
441
|
+
var StaticToolHost = z3.custom(
|
|
422
442
|
(value) => StaticToolFields.safeParse(value).success,
|
|
423
|
-
{ error: "@mcp-b/webmcp-local-relay no longer has mcpServer.registerTool, a registered webmcp_call_tool handler and staticToolData" }
|
|
443
|
+
{ error: "@mcp-b/webmcp-local-relay no longer has mcpServer.registerTool, a registered webmcp_call_tool handler, an SDK server with instructions and staticToolData" }
|
|
424
444
|
);
|
|
425
445
|
function registerYichusTools(server, ctx) {
|
|
426
446
|
const host = StaticToolHost.parse(server);
|
|
447
|
+
host.mcpServer.server._instructions = relayInstructions(ctx.baseUrl);
|
|
427
448
|
registerStatic(host, {
|
|
428
449
|
name: "yichus_save_calculator",
|
|
429
450
|
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.",
|
|
@@ -440,9 +461,9 @@ function registerYichusTools(server, ctx) {
|
|
|
440
461
|
});
|
|
441
462
|
registerStatic(host, {
|
|
442
463
|
name: "yichus_list_calculators",
|
|
443
|
-
description: "List the curated Yichus
|
|
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.",
|
|
444
465
|
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
445
|
-
input:
|
|
466
|
+
input: z3.object({}),
|
|
446
467
|
handler: async () => {
|
|
447
468
|
const entries = listCalculators().map((calc) => ({
|
|
448
469
|
slug: calc.slug,
|
|
@@ -459,11 +480,11 @@ function registerYichusTools(server, ctx) {
|
|
|
459
480
|
});
|
|
460
481
|
registerStatic(host, {
|
|
461
482
|
name: "yichus_open_calculator",
|
|
462
|
-
description: "Open a curated Yichus
|
|
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.",
|
|
463
484
|
annotations: { openWorldHint: true },
|
|
464
|
-
input:
|
|
465
|
-
slug:
|
|
466
|
-
'
|
|
485
|
+
input: z3.object({
|
|
486
|
+
slug: z3.string().describe(
|
|
487
|
+
'Data app slug from yichus_list_calculators (for example "loan", "investment", "tax", "playground").'
|
|
467
488
|
)
|
|
468
489
|
}),
|
|
469
490
|
handler: async (args) => {
|
|
@@ -474,7 +495,7 @@ function registerYichusTools(server, ctx) {
|
|
|
474
495
|
content: [
|
|
475
496
|
{
|
|
476
497
|
type: "text",
|
|
477
|
-
text: `Unknown
|
|
498
|
+
text: `Unknown data app slug "${args.slug}". Known slugs: ${known}.`
|
|
478
499
|
}
|
|
479
500
|
],
|
|
480
501
|
isError: true
|
|
@@ -520,7 +541,7 @@ function registerStatic(host, spec) {
|
|
|
520
541
|
host.staticToolData.set(name, {
|
|
521
542
|
name,
|
|
522
543
|
description,
|
|
523
|
-
inputSchema:
|
|
544
|
+
inputSchema: z3.toJSONSchema(input, { unrepresentable: "any" }),
|
|
524
545
|
annotations
|
|
525
546
|
});
|
|
526
547
|
}
|
|
@@ -529,7 +550,7 @@ function normalizeBaseUrl(raw) {
|
|
|
529
550
|
}
|
|
530
551
|
|
|
531
552
|
// src/cli.ts
|
|
532
|
-
var Port =
|
|
553
|
+
var Port = z4.coerce.number().int().min(1).max(65535);
|
|
533
554
|
function parseFlags(argv) {
|
|
534
555
|
const { values } = parseArgs({ args: argv, strict: true, options: {
|
|
535
556
|
"base-url": { type: "string" },
|
|
@@ -560,7 +581,7 @@ function printHelp() {
|
|
|
560
581
|
|
|
561
582
|
Yichus MCP server: bundles the WebMCP local relay with three static tools
|
|
562
583
|
(yichus_list_calculators, yichus_open_calculator, yichus_save_calculator) so an MCP client
|
|
563
|
-
can drive any deployed Yichus
|
|
584
|
+
can drive any deployed Yichus data app end to end.
|
|
564
585
|
|
|
565
586
|
Options:
|
|
566
587
|
--base-url Yichus deployment to talk to (default: https://yich.us).
|
|
@@ -577,14 +598,13 @@ Options:
|
|
|
577
598
|
built to hint the same values.
|
|
578
599
|
--help, -h Show this help.
|
|
579
600
|
|
|
580
|
-
|
|
581
|
-
Then add to your MCP client config (replace the absolute checkout path):
|
|
601
|
+
Add to your MCP client config:
|
|
582
602
|
|
|
583
603
|
{
|
|
584
604
|
"mcpServers": {
|
|
585
605
|
"yichus": {
|
|
586
|
-
"command": "
|
|
587
|
-
"args": ["
|
|
606
|
+
"command": "npx",
|
|
607
|
+
"args": ["-y", "@yichus/mcp@0.1.3"]
|
|
588
608
|
}
|
|
589
609
|
}
|
|
590
610
|
}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/server.ts","../src/calculators.ts","../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\nBuild from the checkout: pnpm install && pnpm run yichus-mcp:build\nThen add to your MCP client config (replace the absolute checkout path):\n\n {\n \"mcpServers\": {\n \"yichus\": {\n \"command\": \"node\",\n \"args\": [\"/absolute/path/to/yichus/packages/yichus-mcp/dist/cli.js\"]\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.1';\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 * 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 _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 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 and staticToolData' },\n);\n\nfunction registerYichusTools(\n server: LocalRelayMcpServer,\n ctx: { baseUrl: string; disableOpener: boolean }\n): void {\n const host = StaticToolHost.parse(server);\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 * GENERATED from deploy/manifest.json by `yichus deploy --emit-calculators`.\n * Do not edit the CALCULATORS array by hand: the manifest row that deploys\n * a calculator is also its catalog entry, so the two cannot drift.\n */\n\nexport interface Calculator {\n /** Stable short identifier used by `yichus_open_calculator`. */\n readonly slug: string;\n /** Human-readable title that matches the page `<title>`. */\n readonly title: string;\n /** One-line description suitable for an agent to choose between calculators. */\n readonly description: string;\n /** Path under the deploy base URL (no leading slash). */\n readonly path: string;\n}\n\nconst DEFAULT_BASE_URL = 'https://yich.us';\n\nexport const CALCULATORS: readonly Calculator[] = [\n {\n slug: 'event-budget',\n title: 'Event budget',\n description: 'Ticket income, event costs, and the amount left over. Change attendance or adapt the cost rules.',\n path: 'demos/simulation/event-budget.html',\n },\n {\n slug: 'rainwater',\n title: 'Rainwater tank',\n description: 'How much water does one rainfall event collect, and how much overflows the tank?',\n path: 'demos/simulation/rainwater.html',\n },\n {\n slug: 'rainwater-history',\n title: 'Rainwater through a dry spell',\n description: 'Run NOAA weather observations 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] as const;\n\nexport const PLAYGROUND: Calculator = {\n slug: 'playground',\n title: 'Yichus Bosatsu Playground',\n description: 'In-browser editor for Bosatsu simulations with live compile and Why explainer.',\n path: 'editor.html',\n} as const;\n\n/** Look up a curated calculator by its slug, including the playground. */\nexport function findCalculator(slug: string): Calculator | undefined {\n if (slug === PLAYGROUND.slug) return PLAYGROUND;\n return CALCULATORS.find((calc) => calc.slug === slug);\n}\n\n/** Returns every entry exposed by `yichus_list_calculators`. */\nexport function listCalculators(): readonly Calculator[] {\n return [...CALCULATORS, PLAYGROUND];\n}\n\n/** Build the full URL for a calculator at the given base. */\nexport function calculatorUrl(calc: Calculator, baseUrl = DEFAULT_BASE_URL): string {\n const base = new URL(baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`);\n return new URL(calc.path, base).toString();\n}\n\n/**\n * Force `?webmcp=1` on a URL so the page loads the WebMCP relay embed.\n *\n * Pages generated by `SimulationGenerator` and `web/editor.html` only inject\n * the relay embed when this flag (or `localStorage.yichus_webmcp = \"on\"`) is\n * present, so an agent that opens a calculator must set it.\n */\nexport function withWebMcpFlag(rawUrl: string): string {\n const url = new URL(rawUrl);\n url.searchParams.set('webmcp', '1');\n return url.toString();\n}\n","import { spawn } from 'node:child_process';\n\n/** 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(),\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.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,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:'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"]}
|
|
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.d.ts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import { LocalRelayMcpServer } from '@mcp-b/webmcp-local-relay';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Curated catalogue of Yichus simulation demos.
|
|
5
|
-
*
|
|
6
|
-
* GENERATED from deploy/manifest.json by `yichus deploy --emit-calculators`.
|
|
7
|
-
* Do not edit the CALCULATORS array by hand: the manifest row that deploys
|
|
8
|
-
* a calculator is also its catalog entry, so the two cannot drift.
|
|
9
|
-
*/
|
|
10
3
|
interface Calculator {
|
|
11
4
|
/** Stable short identifier used by `yichus_open_calculator`. */
|
|
12
5
|
readonly slug: string;
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// src/server.ts
|
|
2
|
-
import { z as
|
|
2
|
+
import { z as z3 } from "zod";
|
|
3
3
|
|
|
4
4
|
// src/calculators.ts
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
// src/calculators.json
|
|
8
|
+
var calculators_default = [
|
|
7
9
|
{
|
|
8
10
|
slug: "event-budget",
|
|
9
11
|
title: "Event budget",
|
|
@@ -19,7 +21,7 @@ var CALCULATORS = [
|
|
|
19
21
|
{
|
|
20
22
|
slug: "rainwater-history",
|
|
21
23
|
title: "Rainwater through a dry spell",
|
|
22
|
-
description: "Run NOAA weather observations through an inspectable daily tank model. Change capacity and demand, inspect captured data, and share a saved report.",
|
|
24
|
+
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.",
|
|
23
25
|
path: "demos/simulation/rainwater-history.html"
|
|
24
26
|
},
|
|
25
27
|
{
|
|
@@ -131,6 +133,16 @@ var CALCULATORS = [
|
|
|
131
133
|
path: "demos/simulation/carbon-abatement.html"
|
|
132
134
|
}
|
|
133
135
|
];
|
|
136
|
+
|
|
137
|
+
// src/calculators.ts
|
|
138
|
+
var CalculatorEntry = z.strictObject({
|
|
139
|
+
slug: z.string(),
|
|
140
|
+
title: z.string(),
|
|
141
|
+
description: z.string(),
|
|
142
|
+
path: z.string()
|
|
143
|
+
});
|
|
144
|
+
var DEFAULT_BASE_URL = "https://yich.us";
|
|
145
|
+
var CALCULATORS = z.array(CalculatorEntry).parse(calculators_default);
|
|
134
146
|
var PLAYGROUND = {
|
|
135
147
|
slug: "playground",
|
|
136
148
|
title: "Yichus Bosatsu Playground",
|
|
@@ -313,25 +325,26 @@ var PageAnswerBridge = class extends RelayBridgeServer {
|
|
|
313
325
|
// src/saveCalculator.ts
|
|
314
326
|
import { mkdir, writeFile } from "fs/promises";
|
|
315
327
|
import { isAbsolute, join } from "path";
|
|
316
|
-
import { z } from "zod";
|
|
317
|
-
var saveCalculatorInput =
|
|
318
|
-
exportToolName:
|
|
319
|
-
exampleId:
|
|
320
|
-
expectedRevision:
|
|
321
|
-
outputDirectory:
|
|
328
|
+
import { z as z2 } from "zod";
|
|
329
|
+
var saveCalculatorInput = z2.object({
|
|
330
|
+
exportToolName: z2.string().regex(/^yichus_example_export(?:_[a-zA-Z0-9_-]+)?$/).describe("Public export tool name from webmcp_list_tools, matched to the intended tab."),
|
|
331
|
+
exampleId: z2.string().min(1),
|
|
332
|
+
expectedRevision: z2.number().int().nonnegative(),
|
|
333
|
+
outputDirectory: z2.string().refine(isAbsolute, "Use an absolute output directory.").describe("A new directory under an existing parent. Existing directories are refused; files are never overwritten.")
|
|
322
334
|
});
|
|
323
|
-
var chunkSchema =
|
|
324
|
-
exampleId:
|
|
325
|
-
revision:
|
|
326
|
-
exportId:
|
|
327
|
-
assetBaseUrl:
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
+
var chunkSchema = z2.object({
|
|
336
|
+
exampleId: z2.string(),
|
|
337
|
+
revision: z2.number().int(),
|
|
338
|
+
exportId: z2.string().min(1),
|
|
339
|
+
assetBaseUrl: z2.url(),
|
|
340
|
+
selfContained: z2.boolean().default(false),
|
|
341
|
+
sources: z2.array(z2.object({ fileName: z2.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*\.bosatsu$/), source: z2.string().max(1e6) })).min(1).max(16),
|
|
342
|
+
html: z2.object({
|
|
343
|
+
fileName: z2.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*\.html$/),
|
|
344
|
+
content: z2.string().min(1).max(32768),
|
|
345
|
+
offset: z2.number().int().nonnegative(),
|
|
346
|
+
totalLength: z2.number().int().positive().max(32 * 1024 * 1024),
|
|
347
|
+
nextOffset: z2.number().int().nonnegative().nullable()
|
|
335
348
|
})
|
|
336
349
|
});
|
|
337
350
|
async function saveCalculator(rawArgs, invoke) {
|
|
@@ -340,7 +353,7 @@ async function saveCalculator(rawArgs, invoke) {
|
|
|
340
353
|
let first;
|
|
341
354
|
let offset = 0;
|
|
342
355
|
for (; ; ) {
|
|
343
|
-
const response =
|
|
356
|
+
const response = z2.object({ isError: z2.boolean().optional(), structuredContent: z2.unknown().optional() }).parse(await invoke(args.exportToolName, {
|
|
344
357
|
exampleId: args.exampleId,
|
|
345
358
|
expectedRevision: args.expectedRevision,
|
|
346
359
|
offset,
|
|
@@ -349,7 +362,7 @@ async function saveCalculator(rawArgs, invoke) {
|
|
|
349
362
|
if (response.isError) throw new Error("Browser export failed. Read the current example feedback before retrying.");
|
|
350
363
|
const chunk = chunkSchema.parse(response.structuredContent);
|
|
351
364
|
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.");
|
|
352
|
-
if (first && (chunk.exportId !== first.exportId || chunk.assetBaseUrl !== first.assetBaseUrl || 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.");
|
|
365
|
+
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.");
|
|
353
366
|
first ??= chunk;
|
|
354
367
|
const end = offset + chunk.html.content.length;
|
|
355
368
|
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.");
|
|
@@ -366,16 +379,16 @@ async function saveCalculator(rawArgs, invoke) {
|
|
|
366
379
|
directory: args.outputDirectory,
|
|
367
380
|
revision: first.revision,
|
|
368
381
|
exportId: first.exportId,
|
|
369
|
-
assetBaseUrl: first.assetBaseUrl,
|
|
382
|
+
...first.selfContained ? {} : { assetBaseUrl: first.assetBaseUrl },
|
|
370
383
|
files: files.map((f) => ({ fileName: f.fileName, path: join(args.outputDirectory, f.fileName), bytes: Buffer.byteLength(f.content) })),
|
|
371
384
|
htmlPath: join(args.outputDirectory, first.html.fileName),
|
|
372
|
-
note: "The HTML starts from configured defaults and loads supporting assets from the site. It requires network access. No program was executed."
|
|
385
|
+
note: first.selfContained ? "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." : "The HTML starts from configured defaults and loads supporting assets from the site. It requires network access. No program was executed."
|
|
373
386
|
};
|
|
374
387
|
}
|
|
375
388
|
|
|
376
389
|
// src/server.ts
|
|
377
390
|
var PACKAGE_NAME = "yichus-mcp";
|
|
378
|
-
var PACKAGE_VERSION = "0.1.
|
|
391
|
+
var PACKAGE_VERSION = "0.1.3";
|
|
379
392
|
function createYichusMcpServer(options = {}) {
|
|
380
393
|
const baseUrl = normalizeBaseUrl(options.baseUrl ?? "https://yich.us");
|
|
381
394
|
const bridgeOverrides = options.bridgeOverrides ?? {};
|
|
@@ -401,23 +414,31 @@ function createYichusMcpServer(options = {}) {
|
|
|
401
414
|
answerRefusalsAsThePageWroteThem(server, bridge);
|
|
402
415
|
return server;
|
|
403
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 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
|
+
|
|
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.`;
|
|
404
420
|
var TOOL_LIST_CHANGED_WINDOW_MS = 100;
|
|
405
421
|
var isFunction = (field) => typeof field === "function";
|
|
406
|
-
var StaticToolFields =
|
|
407
|
-
mcpServer:
|
|
408
|
-
|
|
422
|
+
var StaticToolFields = z3.object({
|
|
423
|
+
mcpServer: z3.object({
|
|
424
|
+
// The MCP SDK's Server keeps the handshake instructions here; the relay
|
|
425
|
+
// takes none of its own. server.test reads them back from initialize, so
|
|
426
|
+
// a release that moves them fails there.
|
|
427
|
+
server: z3.custom((value) => typeof value === "object" && value !== null && "_instructions" in value),
|
|
428
|
+
registerTool: z3.custom(isFunction),
|
|
409
429
|
// The MCP SDK's record of each registered tool; the relay's
|
|
410
430
|
// webmcp_call_tool is wrapped there (answerRefusalsAsThePageWroteThem).
|
|
411
|
-
_registeredTools:
|
|
431
|
+
_registeredTools: z3.object({ webmcp_call_tool: z3.object({ handler: z3.custom(isFunction) }) })
|
|
412
432
|
}),
|
|
413
|
-
staticToolData:
|
|
433
|
+
staticToolData: z3.instanceof(Map)
|
|
414
434
|
});
|
|
415
|
-
var StaticToolHost =
|
|
435
|
+
var StaticToolHost = z3.custom(
|
|
416
436
|
(value) => StaticToolFields.safeParse(value).success,
|
|
417
|
-
{ error: "@mcp-b/webmcp-local-relay no longer has mcpServer.registerTool, a registered webmcp_call_tool handler and staticToolData" }
|
|
437
|
+
{ error: "@mcp-b/webmcp-local-relay no longer has mcpServer.registerTool, a registered webmcp_call_tool handler, an SDK server with instructions and staticToolData" }
|
|
418
438
|
);
|
|
419
439
|
function registerYichusTools(server, ctx) {
|
|
420
440
|
const host = StaticToolHost.parse(server);
|
|
441
|
+
host.mcpServer.server._instructions = relayInstructions(ctx.baseUrl);
|
|
421
442
|
registerStatic(host, {
|
|
422
443
|
name: "yichus_save_calculator",
|
|
423
444
|
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.",
|
|
@@ -434,9 +455,9 @@ function registerYichusTools(server, ctx) {
|
|
|
434
455
|
});
|
|
435
456
|
registerStatic(host, {
|
|
436
457
|
name: "yichus_list_calculators",
|
|
437
|
-
description: "List the curated Yichus
|
|
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.",
|
|
438
459
|
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
439
|
-
input:
|
|
460
|
+
input: z3.object({}),
|
|
440
461
|
handler: async () => {
|
|
441
462
|
const entries = listCalculators().map((calc) => ({
|
|
442
463
|
slug: calc.slug,
|
|
@@ -453,11 +474,11 @@ function registerYichusTools(server, ctx) {
|
|
|
453
474
|
});
|
|
454
475
|
registerStatic(host, {
|
|
455
476
|
name: "yichus_open_calculator",
|
|
456
|
-
description: "Open a curated Yichus
|
|
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.",
|
|
457
478
|
annotations: { openWorldHint: true },
|
|
458
|
-
input:
|
|
459
|
-
slug:
|
|
460
|
-
'
|
|
479
|
+
input: z3.object({
|
|
480
|
+
slug: z3.string().describe(
|
|
481
|
+
'Data app slug from yichus_list_calculators (for example "loan", "investment", "tax", "playground").'
|
|
461
482
|
)
|
|
462
483
|
}),
|
|
463
484
|
handler: async (args) => {
|
|
@@ -468,7 +489,7 @@ function registerYichusTools(server, ctx) {
|
|
|
468
489
|
content: [
|
|
469
490
|
{
|
|
470
491
|
type: "text",
|
|
471
|
-
text: `Unknown
|
|
492
|
+
text: `Unknown data app slug "${args.slug}". Known slugs: ${known}.`
|
|
472
493
|
}
|
|
473
494
|
],
|
|
474
495
|
isError: true
|
|
@@ -514,7 +535,7 @@ function registerStatic(host, spec) {
|
|
|
514
535
|
host.staticToolData.set(name, {
|
|
515
536
|
name,
|
|
516
537
|
description,
|
|
517
|
-
inputSchema:
|
|
538
|
+
inputSchema: z3.toJSONSchema(input, { unrepresentable: "any" }),
|
|
518
539
|
annotations
|
|
519
540
|
});
|
|
520
541
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/calculators.ts","../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.1';\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 * 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 _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 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 and staticToolData' },\n);\n\nfunction registerYichusTools(\n server: LocalRelayMcpServer,\n ctx: { baseUrl: string; disableOpener: boolean }\n): void {\n const host = StaticToolHost.parse(server);\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 * GENERATED from deploy/manifest.json by `yichus deploy --emit-calculators`.\n * Do not edit the CALCULATORS array by hand: the manifest row that deploys\n * a calculator is also its catalog entry, so the two cannot drift.\n */\n\nexport interface Calculator {\n /** Stable short identifier used by `yichus_open_calculator`. */\n readonly slug: string;\n /** Human-readable title that matches the page `<title>`. */\n readonly title: string;\n /** One-line description suitable for an agent to choose between calculators. */\n readonly description: string;\n /** Path under the deploy base URL (no leading slash). */\n readonly path: string;\n}\n\nconst DEFAULT_BASE_URL = 'https://yich.us';\n\nexport const CALCULATORS: readonly Calculator[] = [\n {\n slug: 'event-budget',\n title: 'Event budget',\n description: 'Ticket income, event costs, and the amount left over. Change attendance or adapt the cost rules.',\n path: 'demos/simulation/event-budget.html',\n },\n {\n slug: 'rainwater',\n title: 'Rainwater tank',\n description: 'How much water does one rainfall event collect, and how much overflows the tank?',\n path: 'demos/simulation/rainwater.html',\n },\n {\n slug: 'rainwater-history',\n title: 'Rainwater through a dry spell',\n description: 'Run NOAA weather observations 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] as const;\n\nexport const PLAYGROUND: Calculator = {\n slug: 'playground',\n title: 'Yichus Bosatsu Playground',\n description: 'In-browser editor for Bosatsu simulations with live compile and Why explainer.',\n path: 'editor.html',\n} as const;\n\n/** Look up a curated calculator by its slug, including the playground. */\nexport function findCalculator(slug: string): Calculator | undefined {\n if (slug === PLAYGROUND.slug) return PLAYGROUND;\n return CALCULATORS.find((calc) => calc.slug === slug);\n}\n\n/** Returns every entry exposed by `yichus_list_calculators`. */\nexport function listCalculators(): readonly Calculator[] {\n return [...CALCULATORS, PLAYGROUND];\n}\n\n/** Build the full URL for a calculator at the given base. */\nexport function calculatorUrl(calc: Calculator, baseUrl = DEFAULT_BASE_URL): string {\n const base = new URL(baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`);\n return new URL(calc.path, base).toString();\n}\n\n/**\n * Force `?webmcp=1` on a URL so the page loads the WebMCP relay embed.\n *\n * Pages generated by `SimulationGenerator` and `web/editor.html` only inject\n * the relay embed when this flag (or `localStorage.yichus_webmcp = \"on\"`) is\n * present, so an agent that opens a calculator must set it.\n */\nexport function withWebMcpFlag(rawUrl: string): string {\n const url = new URL(rawUrl);\n url.searchParams.set('webmcp', '1');\n return url.toString();\n}\n","import { spawn } from 'node:child_process';\n\n/** 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(),\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.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,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:'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"]}
|
|
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.
|
|
4
|
-
"description": "MCP server that exposes the Yichus
|
|
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": {
|