@yichus/mcp 0.1.0 → 0.1.1
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 +80 -20
- package/dist/cli.js +323 -114
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +18 -7
- package/dist/index.js +290 -64
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
- package/LICENSE +0 -21
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/calculators.ts","../src/openUrl.ts","../src/cli.ts"],"sourcesContent":["import { LocalRelayMcpServer } from '@mcp-b/webmcp-local-relay';\nimport { z } from 'zod';\nimport {\n CALCULATORS,\n PLAYGROUND,\n calculatorUrl,\n findCalculator,\n listCalculators,\n withWebMcpFlag,\n type Calculator,\n} from './calculators.js';\nimport { openUrlInBrowser } from './openUrl.js';\n\nconst PACKAGE_NAME = 'yichus-mcp';\n// Bumped via packages/yichus-mcp/package.json. Kept inline so the bin works\n// without reading package.json from disk at startup.\nconst PACKAGE_VERSION = '0.1.0';\n\nexport interface YichusMcpServerOptions {\n /**\n * Origin of the deployed Yichus instance. The relay rejects browser\n * connections from any other origin and `yichus_open_calculator` builds\n * URLs against this base.\n *\n * @defaultValue `https://yich.us`\n */\n baseUrl?: string;\n\n /**\n * Override the WebMCP local-relay options that get passed straight through\n * to the bundled `LocalRelayMcpServer`. Use this to change the bind port,\n * label, workspace name, or relay id.\n *\n * The page-side embed (`web/webmcp-autocomplete.js`, `ensureRelayEmbed`,\n * and the generated calculator pages) hints the widget a workspace and a\n * relay id, and the widget refuses any relay whose discovery hello\n * differs from either. Every shipped page hints `yichus` for both, so the\n * defaults here are the one agreement; override both sides together.\n */\n bridgeOverrides?: {\n host?: string;\n port?: number;\n label?: string;\n workspace?: string;\n relayId?: string;\n };\n\n /**\n * If `true`, the `yichus_open_calculator` tool returns the URL it would\n * have opened without actually launching a browser process. Used by tests\n * and by anyone running the server in a headless environment.\n *\n * @defaultValue `false`\n */\n disableOpener?: boolean;\n}\n\n/**\n * Construct a fully-wired Yichus MCP server.\n *\n * The returned `LocalRelayMcpServer` already has every default WebMCP relay\n * tool plus the two yichus-curated tools (`yichus_list_calculators` and\n * `yichus_open_calculator`) registered. Call `start()` then `startStdio()`\n * (or `connect(transport)`) to bring it online.\n */\nexport function createYichusMcpServer(options: YichusMcpServerOptions = {}): LocalRelayMcpServer {\n const baseUrl = normalizeBaseUrl(options.baseUrl ?? 'https://yich.us');\n const bridgeOverrides = options.bridgeOverrides ?? {};\n\n const server = new LocalRelayMcpServer({\n serverName: PACKAGE_NAME,\n serverVersion: PACKAGE_VERSION,\n bridgeOptions: {\n host: bridgeOverrides.host,\n port: bridgeOverrides.port,\n label: bridgeOverrides.label ?? 'Yichus',\n // The pair every page hints the widget (web/webmcp-autocomplete.js,\n // the generated calculators); the widget rejects any other hello.\n workspace: bridgeOverrides.workspace ?? 'yichus',\n relayId: bridgeOverrides.relayId ?? 'yichus',\n allowedOrigins: [baseUrl],\n },\n });\n\n registerYichusTools(server, { baseUrl, disableOpener: options.disableOpener ?? false });\n return server;\n}\n\ninterface RelayInternals {\n mcpServer: {\n registerTool: (\n name: string,\n config: Record<string, unknown>,\n handler: (args: unknown) => Promise<unknown> | unknown\n ) => unknown;\n };\n staticToolData: Map<\n string,\n {\n name: string;\n description?: string;\n inputSchema: object;\n annotations?: object;\n }\n >;\n}\n\n/**\n * `@mcp-b/webmcp-local-relay@2.2.1` marks `mcpServer` and `staticToolData`\n * as TypeScript-private but the JavaScript fields are public. We pin the\n * relay version exactly so an upstream rename here is a deliberate audit\n * step rather than a silent breakage.\n */\nfunction asInternal(server: LocalRelayMcpServer): RelayInternals {\n return server as unknown as RelayInternals;\n}\n\nfunction registerYichusTools(\n server: LocalRelayMcpServer,\n ctx: { baseUrl: string; disableOpener: boolean }\n): void {\n const internal = asInternal(server);\n\n registerStatic(internal, {\n name: 'yichus_list_calculators',\n description:\n 'List the curated Yichus calculator demos and the playground editor available on the configured Yichus origin. Returns a slug, title, description, and absolute URL for each.',\n annotations: { readOnlyHint: true, idempotentHint: true },\n inputSchemaJson: { type: 'object', properties: {}, additionalProperties: false },\n inputSchemaZod: {} as z.ZodRawShape,\n handler: async () => {\n const entries = listCalculators().map((calc) => ({\n slug: calc.slug,\n title: calc.title,\n description: calc.description,\n url: calculatorUrl(calc, ctx.baseUrl),\n }));\n const payload = { baseUrl: ctx.baseUrl, calculators: entries };\n return {\n content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],\n structuredContent: payload,\n };\n },\n });\n\n registerStatic(internal, {\n name: 'yichus_open_calculator',\n description:\n \"Open a curated Yichus calculator (or the playground) in the user's default browser with WebMCP enabled (?webmcp=1). After opening, the relay tools below will see the page's tools (yichus_list_inputs, yichus_set_inputs, yichus_list_outputs, yichus_explain_output for calculators; yichus_playground_* for the playground). Use yichus_list_calculators to discover slugs.\",\n annotations: { openWorldHint: true },\n inputSchemaJson: {\n type: 'object',\n properties: {\n slug: {\n type: 'string',\n description:\n 'Calculator slug from yichus_list_calculators (for example \"loan\", \"investment\", \"tax\", \"playground\").',\n },\n },\n required: ['slug'],\n additionalProperties: false,\n },\n inputSchemaZod: { slug: z.string() },\n handler: async (rawArgs) => {\n const args = z.object({ slug: z.string() }).parse(rawArgs);\n const calc: Calculator | undefined = findCalculator(args.slug);\n if (!calc) {\n const known = listCalculators().map((c) => c.slug).join(', ');\n return {\n content: [\n {\n type: 'text',\n text: `Unknown calculator slug \"${args.slug}\". Known slugs: ${known}.`,\n },\n ],\n isError: true,\n };\n }\n const url = withWebMcpFlag(calculatorUrl(calc, ctx.baseUrl));\n let opener: { command: string; args: string[] } | null = null;\n if (!ctx.disableOpener) {\n opener = openUrlInBrowser(url);\n }\n const payload = {\n calculator: {\n slug: calc.slug,\n title: calc.title,\n description: calc.description,\n },\n url,\n opened: !ctx.disableOpener,\n opener,\n };\n return {\n content: [\n {\n type: 'text',\n text: ctx.disableOpener\n ? `Would open ${calc.title} at ${url} (browser opener disabled).`\n : `Opening ${calc.title} at ${url}.`,\n },\n ],\n structuredContent: payload,\n };\n },\n });\n}\n\ninterface StaticToolSpec {\n name: string;\n description: string;\n annotations?: Record<string, unknown>;\n inputSchemaJson: object;\n inputSchemaZod: z.ZodRawShape;\n handler: (args: unknown) => Promise<unknown> | unknown;\n}\n\nfunction registerStatic(internal: RelayInternals, spec: StaticToolSpec): void {\n const config: Record<string, unknown> = {\n description: spec.description,\n inputSchema: spec.inputSchemaZod,\n };\n if (spec.annotations && Object.keys(spec.annotations).length > 0) {\n config.annotations = spec.annotations;\n }\n internal.mcpServer.registerTool(spec.name, config, spec.handler);\n internal.staticToolData.set(spec.name, {\n name: spec.name,\n description: spec.description,\n inputSchema: spec.inputSchemaJson,\n ...(spec.annotations && Object.keys(spec.annotations).length > 0\n ? { annotations: spec.annotations }\n : {}),\n });\n}\n\nfunction normalizeBaseUrl(raw: string): string {\n // `URL` validates the input and re-serializes to the canonical form.\n return new URL(raw).origin;\n}\n\nexport const _testing = {\n CALCULATORS,\n PLAYGROUND,\n asInternal,\n};\n","/**\n * Curated catalogue of Yichus simulation demos.\n *\n * GENERATED from deploy/manifest.json by `yichus deploy --emit-calculators`.\n * Do not edit the CALCULATORS array by hand: the manifest row that deploys\n * a calculator is also its catalog entry, so the two cannot drift.\n */\n\nexport interface Calculator {\n /** Stable short identifier used by `yichus_open_calculator`. */\n readonly slug: string;\n /** Human-readable title that matches the page `<title>`. */\n readonly title: string;\n /** One-line description suitable for an agent to choose between calculators. */\n readonly description: string;\n /** Path under the deploy base URL (no leading slash). */\n readonly path: string;\n}\n\nconst DEFAULT_BASE_URL = 'https://yich.us';\n\nexport const CALCULATORS: readonly Calculator[] = [\n {\n slug: 'event-budget',\n title: 'Event budget',\n description: 'Ticket income, event costs, and the amount left over. Change attendance or adapt the cost rules.',\n path: 'demos/simulation/event-budget.html',\n },\n {\n slug: 'rainwater',\n title: 'Rainwater tank',\n description: 'How much water does one rainfall event collect, and how much overflows the tank?',\n path: 'demos/simulation/rainwater.html',\n },\n {\n slug: 'battery-runtime',\n title: 'Battery runtime',\n description: 'Estimate runtime from a battery energy budget, a constant load, and a usable-capacity assumption.',\n path: 'demos/simulation/battery-runtime.html',\n },\n {\n slug: 'recipe-scale',\n title: 'Scale a recipe',\n description: 'Scale ingredient weights to a different number of servings. Replace the ingredients with your own.',\n path: 'demos/simulation/recipe-scale.html',\n },\n {\n slug: 'download-time',\n title: 'Download time',\n description: 'Convert file size and connection speed into transfer time, allowing for a chosen efficiency.',\n path: 'demos/simulation/download-time.html',\n },\n {\n slug: 'loan',\n title: 'Loan Calculator',\n description: 'Fixed monthly payment and total interest for a loan repaid in equal monthly installments.',\n path: 'demos/simulation/loan-calculator.html',\n },\n {\n slug: 'investment',\n title: 'Investment Growth Calculator',\n description: 'Compound growth of a recurring investment under configurable return assumptions.',\n path: 'demos/simulation/investment.html',\n },\n {\n slug: 'tax',\n title: 'Tax Calculator',\n description: 'Flat-rate example: subtract a deduction and apply one percentage. No tax brackets or jurisdiction-specific rules.',\n path: 'demos/simulation/tax-calculator.html',\n },\n {\n slug: 'carbon',\n title: 'Carbon Footprint Calculator',\n description: 'Illustrative annual CO2 totals from driving, flights, electricity, and gas, using fixed emission factors.',\n path: 'demos/simulation/carbon-footprint.html',\n },\n {\n slug: 'rate',\n title: 'Rate Calculator',\n description: 'Apply a basis-point rate to an amount, then subtract a percentage fee. Inspect both helper calculations in Why.',\n path: 'demos/simulation/rate-calculator.html',\n },\n {\n slug: 'compound-fold',\n title: 'CompoundFold',\n description: 'Compound a starting amount over several periods, increasing the rate by 0.1 percentage points each period.',\n path: 'demos/simulation/compound-fold.html',\n },\n {\n slug: 'closure-capture',\n title: 'ClosureCapture',\n description: 'Scale two amounts with the same input rate captured by a shared inner function.',\n path: 'demos/simulation/closure-capture.html',\n },\n {\n slug: 'lambda-factory',\n title: 'Fee pipeline',\n description: 'Apply processing, platform, and regulatory fees in sequence, with each helper capturing its chosen rate.',\n path: 'demos/simulation/lambda-factory-selector.html',\n },\n {\n slug: 'portfolio',\n title: 'Three growth rates',\n description: 'Split an amount into three equal parts and apply a chosen growth rate to each. This example does not estimate risk.',\n path: 'demos/simulation/portfolio-analysis.html',\n },\n {\n slug: 'strategy-portfolio',\n title: 'Choosing a growth multiplier',\n description: 'Choose between three illustrative growth multipliers. This is a branching example, not an investment-risk model.',\n path: 'demos/simulation/strategy-portfolio.html',\n },\n {\n slug: 'tiered-policy',\n title: 'Claim payout rules',\n description: 'A toy claim payout: choose an incident severity and policy tier, then inspect the adjustments and deduction.',\n path: 'demos/simulation/tiered-policy-engine.html',\n },\n {\n slug: 'what-if-path-switch',\n title: 'WhatIfPathSwitch',\n description: 'Switches computation paths via assumption toggles (\"what-if\" branches).',\n path: 'demos/simulation/what-if-path-switch.html',\n },\n {\n slug: 'deadweight_loss',\n title: 'Deadweight Loss Calculator',\n description: 'Equilibrium, surplus, and deadweight loss of a per-unit tax on a linear market.',\n path: 'demos/simulation/deadweight-loss.html',\n },\n {\n slug: 'trend_projection',\n title: 'Vintage Forecasts',\n description: 'An illustrative series with synthetic forecast vintages. Compare compounding assumptions; these are model-generated values, not agency data.',\n path: 'demos/simulation/trend-projection.html',\n },\n {\n slug: 'carbon_abatement',\n title: 'Carbon Abatement Path',\n description: 'A rising marginal abatement cost path as a sampled curve, with derived cheapest-step and tranche-cost marks.',\n path: 'demos/simulation/carbon-abatement.html',\n },\n] as const;\n\nexport const PLAYGROUND: Calculator = {\n slug: 'playground',\n title: 'Yichus Bosatsu Playground',\n description: 'In-browser editor for Bosatsu simulations with live compile and Why explainer.',\n path: 'editor.html',\n} as const;\n\n/** Look up a curated calculator by its slug, including the playground. */\nexport function findCalculator(slug: string): Calculator | undefined {\n if (slug === PLAYGROUND.slug) return PLAYGROUND;\n return CALCULATORS.find((calc) => calc.slug === slug);\n}\n\n/** Returns every entry exposed by `yichus_list_calculators`. */\nexport function listCalculators(): readonly Calculator[] {\n return [...CALCULATORS, PLAYGROUND];\n}\n\n/** Build the full URL for a calculator at the given base. */\nexport function calculatorUrl(calc: Calculator, baseUrl = DEFAULT_BASE_URL): string {\n const base = new URL(baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`);\n return new URL(calc.path, base).toString();\n}\n\n/**\n * Force `?webmcp=1` on a URL so the page loads the WebMCP relay embed.\n *\n * Pages generated by `SimulationGenerator` and `web/editor.html` only inject\n * the relay embed when this flag (or `localStorage.yichus_webmcp = \"on\"`) is\n * present, so an agent that opens a calculator must set it.\n */\nexport function withWebMcpFlag(rawUrl: string): string {\n const url = new URL(rawUrl);\n url.searchParams.set('webmcp', '1');\n return url.toString();\n}\n","import { spawn } from 'node:child_process';\n\n/**\n * Opens a URL in the user's default browser.\n *\n * Uses `URL.href` so shell metacharacters in the original string are escaped\n * before they reach the platform opener. Mirrors the approach the upstream\n * `webmcp-local-relay` `webmcp_open_page` tool uses.\n */\nexport function openUrlInBrowser(rawUrl: string): { command: string; args: string[] } {\n const validated = new URL(rawUrl).href;\n const platform = process.platform;\n\n let command: string;\n let args: string[];\n if (platform === 'darwin') {\n command = 'open';\n args = [validated];\n } else if (platform === 'win32') {\n command = 'powershell';\n args = ['-NoProfile', '-NonInteractive', '-Command', `Start-Process '${validated.replace(/'/g, \"''\")}'`];\n } else {\n command = 'xdg-open';\n args = [validated];\n }\n\n spawn(command, args, { detached: true, stdio: 'ignore' }).unref();\n return { command, args };\n}\n","#!/usr/bin/env node\nimport { createYichusMcpServer } from './server.js';\n\ninterface ParsedFlags {\n baseUrl?: string;\n port?: number;\n host?: string;\n label?: string;\n workspace?: string;\n relayId?: string;\n}\n\nfunction parseFlags(argv: string[]): ParsedFlags {\n const flags: ParsedFlags = {};\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const next = (): string => {\n const value = argv[i + 1];\n if (value === undefined) {\n throw new Error(`Missing value for ${arg}`);\n }\n i += 1;\n return value;\n };\n switch (arg) {\n case '--base-url':\n flags.baseUrl = next();\n break;\n case '--host':\n flags.host = next();\n break;\n case '--port': {\n const value = next();\n const parsed = Number.parseInt(value, 10);\n if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {\n throw new Error(`Invalid --port value: ${value}`);\n }\n flags.port = parsed;\n break;\n }\n case '--label':\n flags.label = next();\n break;\n case '--workspace':\n flags.workspace = next();\n break;\n case '--relay-id':\n flags.relayId = next();\n break;\n case '--help':\n case '-h':\n printHelp();\n process.exit(0);\n // eslint-disable-next-line no-fallthrough\n default:\n throw new Error(`Unknown flag: ${arg}`);\n }\n }\n return flags;\n}\n\nfunction printHelp(): void {\n process.stdout.write(`yichus-mcp [options]\n\nYichus MCP server: bundles the WebMCP local relay with two extra static\ntools (yichus_list_calculators, yichus_open_calculator) so an MCP client\ncan drive any deployed Yichus calculator end to end.\n\nOptions:\n --base-url Yichus deployment to talk to (default: https://yich.us).\n Used as the relay --widget-origin and as the URL base for\n yichus_open_calculator.\n --host Bridge bind host (default: 127.0.0.1).\n --port Bridge WebSocket port (default: 9333, falls back to 9334+).\n --label Human label reported during relay discovery (default: \"Yichus\").\n --workspace Workspace name reported during relay discovery (default: \"yichus\").\n --relay-id Relay id reported during relay discovery (default: \"yichus\").\n Every Yichus page hints the widget workspace \"yichus\" and\n relay id \"yichus\" and the widget rejects a relay whose\n announcement differs, so change these only for a page\n built to hint the same values.\n --help, -h Show this help.\n\nAdd to your MCP client config (Claude Desktop, Cursor, etc.):\n\n {\n \"mcpServers\": {\n \"yichus\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@yichus/mcp\"]\n }\n }\n }\n`);\n}\n\nasync function main(): Promise<void> {\n let flags: ParsedFlags;\n try {\n flags = parseFlags(process.argv.slice(2));\n } catch (err) {\n process.stderr.write(`[yichus-mcp] ${err instanceof Error ? err.message : String(err)}\\n`);\n printHelp();\n process.exit(2);\n }\n\n const server = createYichusMcpServer({\n baseUrl: flags.baseUrl,\n bridgeOverrides: {\n host: flags.host,\n port: flags.port,\n label: flags.label,\n workspace: flags.workspace,\n relayId: flags.relayId,\n },\n });\n\n const shutdown = async (): Promise<void> => {\n try {\n await server.stop();\n } catch (err) {\n process.stderr.write(\n `[yichus-mcp] warn: shutdown error: ${err instanceof Error ? err.message : String(err)}\\n`\n );\n }\n };\n process.on('SIGINT', () => {\n void shutdown().then(() => process.exit(0));\n });\n process.on('SIGTERM', () => {\n void shutdown().then(() => process.exit(0));\n });\n\n await server.start();\n await server.startStdio();\n}\n\nmain().catch((err) => {\n process.stderr.write(\n `[yichus-mcp] fatal: ${err instanceof Error ? err.stack ?? err.message : String(err)}\\n`\n );\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
|
|
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"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -74,22 +74,33 @@ interface YichusMcpServerOptions {
|
|
|
74
74
|
* Construct a fully-wired Yichus MCP server.
|
|
75
75
|
*
|
|
76
76
|
* The returned `LocalRelayMcpServer` already has every default WebMCP relay
|
|
77
|
-
* tool plus
|
|
78
|
-
*
|
|
77
|
+
* tool plus Yichus calculator discovery, opening, and saving tools
|
|
78
|
+
* registered. Call `start()` then `startStdio()`
|
|
79
79
|
* (or `connect(transport)`) to bring it online.
|
|
80
80
|
*/
|
|
81
81
|
declare function createYichusMcpServer(options?: YichusMcpServerOptions): LocalRelayMcpServer;
|
|
82
82
|
|
|
83
|
+
/** What happened when the relay asked the platform opener for a URL. */
|
|
84
|
+
interface OpenAttempt {
|
|
85
|
+
command: string;
|
|
86
|
+
args: string[];
|
|
87
|
+
/** True once the opener process started; false when it could not be started. */
|
|
88
|
+
opened: boolean;
|
|
89
|
+
/** Why the opener could not be started (for example `spawn xdg-open ENOENT`). */
|
|
90
|
+
error?: string;
|
|
91
|
+
}
|
|
83
92
|
/**
|
|
84
93
|
* Opens a URL in the user's default browser.
|
|
85
94
|
*
|
|
86
95
|
* Uses `URL.href` so shell metacharacters in the original string are escaped
|
|
87
96
|
* before they reach the platform opener. Mirrors the approach the upstream
|
|
88
97
|
* `webmcp-local-relay` `webmcp_open_page` tool uses.
|
|
98
|
+
*
|
|
99
|
+
* Resolves only once the opener has started or failed to start, so the
|
|
100
|
+
* answer reports what happened. A machine with no opener (a container, a
|
|
101
|
+
* headless box) answers `opened: false` instead of an unhandled `error`
|
|
102
|
+
* event, which would end the relay and every tool with it.
|
|
89
103
|
*/
|
|
90
|
-
declare function openUrlInBrowser(rawUrl: string):
|
|
91
|
-
command: string;
|
|
92
|
-
args: string[];
|
|
93
|
-
};
|
|
104
|
+
declare function openUrlInBrowser(rawUrl: string): Promise<OpenAttempt>;
|
|
94
105
|
|
|
95
|
-
export { CALCULATORS, type Calculator, PLAYGROUND, type YichusMcpServerOptions, calculatorUrl, createYichusMcpServer, findCalculator, listCalculators, openUrlInBrowser, withWebMcpFlag };
|
|
106
|
+
export { CALCULATORS, type Calculator, type OpenAttempt, PLAYGROUND, type YichusMcpServerOptions, calculatorUrl, createYichusMcpServer, findCalculator, listCalculators, openUrlInBrowser, withWebMcpFlag };
|