@olonjs/cli 3.0.164 → 3.0.165

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.
@@ -1817,9 +1817,9 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
1817
1817
  "@tiptap/extension-link": "^2.11.5",
1818
1818
  "@tiptap/react": "^2.11.5",
1819
1819
  "@tiptap/starter-kit": "^2.11.5",
1820
- "@olonjs/core": "^1.1.32",
1821
- "@olonjs/react": "^0.1.15",
1822
- "@olonjs/studio": "^0.1.15",
1820
+ "@olonjs/core": "^2.0.1",
1821
+ "@olonjs/react": "^0.2.1",
1822
+ "@olonjs/studio": "^0.2.1",
1823
1823
  "class-variance-authority": "^0.7.1",
1824
1824
  "clsx": "^2.1.1",
1825
1825
  "lucide-react": "^0.474.0",
@@ -1833,7 +1833,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
1833
1833
  "remark-gfm": "^4.0.1",
1834
1834
  "tailwind-merge": "^3.0.1",
1835
1835
  "tiptap-markdown": "^0.8.10",
1836
- "zod": "^3.24.1"
1836
+ "zod": "^4.6.0"
1837
1837
  },
1838
1838
  "devDependencies": {
1839
1839
  "@tailwindcss/vite": "^4.0.0",
@@ -3032,304 +3032,302 @@ export function tenantDevApiPlugin(options: TenantDevApiPluginOptions): Plugin {
3032
3032
  END_OF_FILE_CONTENT
3033
3033
  echo "Creating scripts/webmcp-feature-check.mjs..."
3034
3034
  cat << 'END_OF_FILE_CONTENT' > "scripts/webmcp-feature-check.mjs"
3035
- import fs from 'fs/promises';
3036
- import path from 'path';
3037
- import { fileURLToPath } from 'url';
3038
- import { createRequire } from 'module';
3039
-
3040
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
3041
- const rootDir = path.resolve(__dirname, '..');
3042
- const baseUrl = process.env.WEBMCP_BASE_URL ?? 'http://127.0.0.1:4173';
3043
-
3044
- function pageFilePathFromSlug(slug) {
3045
- return path.resolve(rootDir, 'src', 'data', 'pages', `${slug}.json`);
3046
- }
3047
-
3048
- function adminUrlFromSlug(slug) {
3049
- return `${baseUrl}/admin${slug === 'home' ? '' : `/${slug}`}`;
3050
- }
3051
-
3052
- function isStringSchema(schema) {
3053
- if (!schema || typeof schema !== 'object') return false;
3054
- if (schema.type === 'string') return true;
3055
- if (Array.isArray(schema.anyOf)) {
3056
- return schema.anyOf.some((entry) => entry && typeof entry === 'object' && entry.type === 'string');
3057
- }
3058
- return false;
3059
- }
3060
-
3061
- function findTopLevelStringField(sectionSchema) {
3062
- const properties = sectionSchema?.properties;
3063
- if (!properties || typeof properties !== 'object') return null;
3064
- const preferred = ['title', 'sectionTitle', 'label', 'headline', 'name'];
3065
- for (const key of preferred) {
3066
- if (isStringSchema(properties[key])) return key;
3067
- }
3068
- for (const [key, value] of Object.entries(properties)) {
3069
- if (isStringSchema(value)) return key;
3070
- }
3071
- return null;
3072
- }
3073
-
3074
- async function loadPlaywright() {
3075
- const require = createRequire(import.meta.url);
3076
- try {
3077
- return require('playwright');
3078
- } catch (error) {
3079
- const message = error instanceof Error ? error.message : String(error);
3080
- throw new Error(
3081
- `Playwright is required for WebMCP verification. Install it before running this script. Original error: ${message}`
3082
- );
3083
- }
3084
- }
3085
-
3086
- async function readPageJson(slug) {
3087
- const pageFilePath = pageFilePathFromSlug(slug);
3088
- const raw = await fs.readFile(pageFilePath, 'utf8');
3089
- return { raw, json: JSON.parse(raw), pageFilePath };
3090
- }
3091
-
3092
- async function waitFor(predicate, timeoutMs, label) {
3093
- const startedAt = Date.now();
3094
- for (;;) {
3095
- const result = await predicate();
3096
- if (result) return result;
3097
- if (Date.now() - startedAt > timeoutMs) {
3098
- throw new Error(`Timed out while waiting for ${label}.`);
3099
- }
3100
- await new Promise((resolve) => setTimeout(resolve, 150));
3101
- }
3102
- }
3103
-
3104
- async function waitForFileFieldValue(slug, sectionId, fieldKey, expectedValue) {
3105
- await waitFor(async () => {
3106
- const { json } = await readPageJson(slug);
3107
- const section = Array.isArray(json.sections)
3108
- ? json.sections.find((item) => item?.id === sectionId)
3109
- : null;
3110
- return section?.data?.[fieldKey] === expectedValue;
3111
- }, 8_000, `file field "${fieldKey}" = "${expectedValue}"`);
3112
- }
3113
-
3114
- async function ensureResponseOk(response, label) {
3115
- if (!response.ok) {
3116
- const text = await response.text();
3117
- throw new Error(`${label} failed with ${response.status}: ${text}`);
3118
- }
3119
- return response;
3120
- }
3121
-
3122
- async function fetchJson(relativePath, label) {
3123
- const response = await ensureResponseOk(await fetch(`${baseUrl}${relativePath}`), label);
3124
- return response.json();
3125
- }
3126
-
3127
- async function selectTarget() {
3128
- const siteIndex = await fetchJson('/mcp-manifest.json', 'Manifest index request');
3129
- const requestedSlug = typeof process.env.WEBMCP_TARGET_SLUG === 'string' && process.env.WEBMCP_TARGET_SLUG.trim()
3130
- ? process.env.WEBMCP_TARGET_SLUG.trim()
3131
- : null;
3132
-
3133
- const candidatePages = requestedSlug
3134
- ? (siteIndex.pages ?? []).filter((page) => page?.slug === requestedSlug)
3135
- : (siteIndex.pages ?? []);
3136
-
3137
- for (const pageEntry of candidatePages) {
3138
- if (!pageEntry?.slug || !pageEntry?.manifestHref || !pageEntry?.contractHref) continue;
3139
- const pageManifest = await fetchJson(pageEntry.manifestHref, `Page manifest request for ${pageEntry.slug}`);
3140
- const pageContract = await fetchJson(pageEntry.contractHref, `Page contract request for ${pageEntry.slug}`);
3141
- const localInstances = Array.isArray(pageContract.sectionInstances)
3142
- ? pageContract.sectionInstances.filter((section) => section?.scope === 'local')
3143
- : [];
3144
- const tools = Array.isArray(pageManifest.tools) ? pageManifest.tools : [];
3145
-
3146
- for (const tool of tools) {
3147
- const sectionType = tool?.sectionType;
3148
- if (typeof tool?.name !== 'string' || typeof sectionType !== 'string') continue;
3149
- const targetInstance = localInstances.find((section) => section?.type === sectionType);
3150
- if (!targetInstance?.id) continue;
3151
- const targetFieldKey = findTopLevelStringField(pageContract.sectionSchemas?.[sectionType]);
3152
- if (!targetFieldKey) continue;
3153
- const pageState = await readPageJson(pageEntry.slug);
3154
- const section = Array.isArray(pageState.json.sections)
3155
- ? pageState.json.sections.find((item) => item?.id === targetInstance.id)
3156
- : null;
3157
- const originalValue = section?.data?.[targetFieldKey];
3158
- if (typeof originalValue !== 'string') continue;
3159
-
3160
- return {
3161
- slug: pageEntry.slug,
3162
- manifestHref: pageEntry.manifestHref,
3163
- contractHref: pageEntry.contractHref,
3164
- toolName: tool.name,
3165
- sectionId: targetInstance.id,
3166
- fieldKey: targetFieldKey,
3167
- originalValue,
3168
- originalState: pageState,
3169
- };
3170
- }
3171
- }
3172
-
3173
- throw new Error(
3174
- requestedSlug
3175
- ? `No valid WebMCP verification target found for page "${requestedSlug}".`
3176
- : 'No valid WebMCP verification target found in manifest index.'
3177
- );
3178
- }
3179
-
3180
- async function main() {
3181
- const { chromium } = await loadPlaywright();
3182
- const target = await selectTarget();
3183
- const nextValue = `${target.originalValue} WebMCP ${Date.now()}`;
3184
- const browser = await chromium.launch({ headless: true });
3185
- const page = await browser.newPage();
3186
- const consoleEvents = [];
3187
- let mutationApplied = false;
3188
-
3189
- page.on('console', (message) => {
3190
- if (message.type() === 'error' || message.type() === 'warning') {
3191
- consoleEvents.push(`[console:${message.type()}] ${message.text()}`);
3192
- }
3193
- });
3194
- page.on('pageerror', (error) => {
3195
- consoleEvents.push(`[pageerror] ${error.message}`);
3196
- });
3197
-
3198
- const restoreOriginal = async () => {
3199
- try {
3200
- await page.evaluate(
3201
- async ({ toolName, slug, sectionId, fieldKey, value }) => {
3202
- const runtime = document.modelContextTesting;
3203
- if (!runtime?.executeTool) return;
3204
- await runtime.executeTool(
3205
- toolName,
3206
- JSON.stringify({
3207
- slug,
3208
- sectionId,
3209
- fieldKey,
3210
- value,
3211
- })
3212
- );
3213
- },
3214
- {
3215
- toolName: target.toolName,
3216
- slug: target.slug,
3217
- sectionId: target.sectionId,
3218
- fieldKey: target.fieldKey,
3219
- value: target.originalValue,
3220
- }
3221
- );
3222
- await waitForFileFieldValue(target.slug, target.sectionId, target.fieldKey, target.originalValue);
3223
- } catch {
3224
- await fs.writeFile(target.originalState.pageFilePath, target.originalState.raw, 'utf8');
3225
- }
3226
- };
3227
-
3228
- try {
3229
- const pageManifest = await fetchJson(target.manifestHref, `Manifest request for ${target.slug}`);
3230
- if (!Array.isArray(pageManifest.tools) || !pageManifest.tools.some((tool) => tool?.name === target.toolName)) {
3231
- throw new Error(`Manifest does not expose ${target.toolName}.`);
3232
- }
3233
-
3234
- const pageContract = await fetchJson(target.contractHref, `Contract request for ${target.slug}`);
3235
- if (!Array.isArray(pageContract.tools) || !pageContract.tools.some((tool) => tool?.name === target.toolName)) {
3236
- throw new Error(`Page contract does not expose ${target.toolName}.`);
3237
- }
3238
-
3239
- await page.goto(adminUrlFromSlug(target.slug), { waitUntil: 'networkidle' });
3240
-
3241
- try {
3242
- await page.waitForFunction(
3243
- ({ manifestHref, contractHref }) => {
3244
- const manifestLink = document.head.querySelector('link[rel="mcp-manifest"]');
3245
- const contractLink = document.head.querySelector('link[rel="olon-contract"]');
3246
- return manifestLink?.getAttribute('href') === manifestHref
3247
- && contractLink?.getAttribute('href') === contractHref;
3248
- },
3249
- { manifestHref: target.manifestHref, contractHref: target.contractHref },
3250
- { timeout: 10_000 }
3251
- );
3252
- } catch (error) {
3253
- const diagnostics = await page.evaluate(() => ({
3254
- head: document.head.innerHTML,
3255
- bodyText: document.body.innerText,
3256
- }));
3257
- throw new Error(
3258
- [
3259
- error instanceof Error ? error.message : String(error),
3260
- `head=${diagnostics.head}`,
3261
- `body=${diagnostics.bodyText}`,
3262
- ...consoleEvents,
3263
- ].join('\n')
3264
- );
3265
- }
3266
-
3267
- const toolNames = await page.evaluate(() => {
3268
- const runtime = document.modelContextTesting;
3269
- return runtime?.listTools?.().map((tool) => tool.name) ?? [];
3270
- });
3271
- if (!toolNames.includes(target.toolName)) {
3272
- throw new Error(`Runtime did not register ${target.toolName}. Found: ${toolNames.join(', ')}`);
3273
- }
3274
-
3275
- const rawResult = await page.evaluate(
3276
- async ({ toolName, slug, sectionId, fieldKey, value }) => {
3277
- const runtime = document.modelContextTesting;
3278
- if (!runtime?.executeTool) {
3279
- throw new Error('document.modelContextTesting.executeTool is unavailable.');
3280
- }
3281
- return runtime.executeTool(
3282
- toolName,
3283
- JSON.stringify({
3284
- slug,
3285
- sectionId,
3286
- fieldKey,
3287
- value,
3288
- })
3289
- );
3290
- },
3291
- {
3292
- toolName: target.toolName,
3293
- slug: target.slug,
3294
- sectionId: target.sectionId,
3295
- fieldKey: target.fieldKey,
3296
- value: nextValue,
3297
- }
3298
- );
3299
-
3300
- const parsedResult = JSON.parse(rawResult);
3301
- if (parsedResult?.isError) {
3302
- throw new Error(`WebMCP tool returned an error: ${rawResult}`);
3303
- }
3304
-
3305
- mutationApplied = true;
3306
- await waitForFileFieldValue(target.slug, target.sectionId, target.fieldKey, nextValue);
3307
- await page.frameLocator('iframe').getByText(nextValue, { exact: true }).waitFor({ state: 'attached' });
3308
-
3309
- console.log(
3310
- JSON.stringify({
3311
- ok: true,
3312
- slug: target.slug,
3313
- manifestHref: target.manifestHref,
3314
- contractHref: target.contractHref,
3315
- toolName: target.toolName,
3316
- sectionId: target.sectionId,
3317
- fieldKey: target.fieldKey,
3318
- toolNames,
3319
- })
3320
- );
3321
- } finally {
3322
- if (mutationApplied) {
3323
- await restoreOriginal();
3324
- }
3325
- await browser.close();
3326
- }
3327
- }
3328
-
3329
- main().catch((error) => {
3330
- console.error(error instanceof Error ? error.stack ?? error.message : String(error));
3331
- process.exit(1);
3332
- });
3035
+ import fs from 'fs/promises';
3036
+ import path from 'path';
3037
+ import { fileURLToPath } from 'url';
3038
+ import { createRequire } from 'module';
3039
+
3040
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
3041
+ const rootDir = path.resolve(__dirname, '..');
3042
+ const baseUrl = process.env.WEBMCP_BASE_URL ?? 'http://127.0.0.1:4173';
3043
+ const UPDATE_TOOL_NAME = 'update-section';
3044
+ const SAVE_TOOL_NAME = 'save';
3045
+
3046
+ function pageFilePathFromSlug(slug) {
3047
+ return path.resolve(rootDir, 'src', 'data', 'pages', `${slug}.json`);
3048
+ }
3049
+
3050
+ function adminUrlFromSlug(slug) {
3051
+ return `${baseUrl}/admin${slug === 'home' ? '' : `/${slug}`}`;
3052
+ }
3053
+
3054
+ function isStringSchema(schema) {
3055
+ if (!schema || typeof schema !== 'object') return false;
3056
+ if (schema.type === 'string') return true;
3057
+ if (Array.isArray(schema.anyOf)) {
3058
+ return schema.anyOf.some((entry) => entry && typeof entry === 'object' && entry.type === 'string');
3059
+ }
3060
+ return false;
3061
+ }
3062
+
3063
+ function findTopLevelStringField(sectionSchema) {
3064
+ const properties = sectionSchema?.properties;
3065
+ if (!properties || typeof properties !== 'object') return null;
3066
+ const preferred = ['title', 'sectionTitle', 'label', 'headline', 'name'];
3067
+ for (const key of preferred) {
3068
+ if (isStringSchema(properties[key])) return key;
3069
+ }
3070
+ for (const [key, value] of Object.entries(properties)) {
3071
+ if (isStringSchema(value)) return key;
3072
+ }
3073
+ return null;
3074
+ }
3075
+
3076
+ async function loadPlaywright() {
3077
+ const require = createRequire(import.meta.url);
3078
+ try {
3079
+ return require('playwright');
3080
+ } catch (error) {
3081
+ const message = error instanceof Error ? error.message : String(error);
3082
+ throw new Error(
3083
+ `Playwright is required for WebMCP verification. Install it before running this script. Original error: ${message}`
3084
+ );
3085
+ }
3086
+ }
3087
+
3088
+ async function readPageJson(slug) {
3089
+ const pageFilePath = pageFilePathFromSlug(slug);
3090
+ const raw = await fs.readFile(pageFilePath, 'utf8');
3091
+ return { raw, json: JSON.parse(raw), pageFilePath };
3092
+ }
3093
+
3094
+ async function waitFor(predicate, timeoutMs, label) {
3095
+ const startedAt = Date.now();
3096
+ for (;;) {
3097
+ const result = await predicate();
3098
+ if (result) return result;
3099
+ if (Date.now() - startedAt > timeoutMs) {
3100
+ throw new Error(`Timed out while waiting for ${label}.`);
3101
+ }
3102
+ await new Promise((resolve) => setTimeout(resolve, 150));
3103
+ }
3104
+ }
3105
+
3106
+ async function waitForFileFieldValue(slug, sectionId, fieldKey, expectedValue) {
3107
+ await waitFor(async () => {
3108
+ const { json } = await readPageJson(slug);
3109
+ const section = Array.isArray(json.sections)
3110
+ ? json.sections.find((item) => item?.id === sectionId)
3111
+ : null;
3112
+ return section?.data?.[fieldKey] === expectedValue;
3113
+ }, 8_000, `file field "${fieldKey}" = "${expectedValue}"`);
3114
+ }
3115
+
3116
+ async function ensureResponseOk(response, label) {
3117
+ if (!response.ok) {
3118
+ const text = await response.text();
3119
+ throw new Error(`${label} failed with ${response.status}: ${text}`);
3120
+ }
3121
+ return response;
3122
+ }
3123
+
3124
+ async function fetchJson(relativePath, label) {
3125
+ const response = await ensureResponseOk(await fetch(`${baseUrl}${relativePath}`), label);
3126
+ return response.json();
3127
+ }
3128
+
3129
+ async function selectTarget() {
3130
+ const siteIndex = await fetchJson('/mcp-manifest.json', 'Manifest index request');
3131
+ const requestedSlug = typeof process.env.WEBMCP_TARGET_SLUG === 'string' && process.env.WEBMCP_TARGET_SLUG.trim()
3132
+ ? process.env.WEBMCP_TARGET_SLUG.trim()
3133
+ : null;
3134
+
3135
+ const candidatePages = requestedSlug
3136
+ ? (siteIndex.pages ?? []).filter((page) => page?.slug === requestedSlug)
3137
+ : (siteIndex.pages ?? []);
3138
+
3139
+ for (const pageEntry of candidatePages) {
3140
+ if (!pageEntry?.slug || !pageEntry?.manifestHref || !pageEntry?.contractHref) continue;
3141
+ const pageManifest = await fetchJson(pageEntry.manifestHref, `Page manifest request for ${pageEntry.slug}`);
3142
+ const pageContract = await fetchJson(pageEntry.contractHref, `Page contract request for ${pageEntry.slug}`);
3143
+ const localInstances = Array.isArray(pageContract.sectionInstances)
3144
+ ? pageContract.sectionInstances.filter((section) => section?.scope === 'local')
3145
+ : [];
3146
+ const tools = Array.isArray(pageManifest.tools) ? pageManifest.tools : [];
3147
+ // Manifest tools are page-level (`update-section`, `save`); targets come from the
3148
+ // contract's local section instances + their schemas.
3149
+ const updateTool = tools.find((tool) => tool?.name === UPDATE_TOOL_NAME);
3150
+ const saveTool = tools.find((tool) => tool?.name === SAVE_TOOL_NAME);
3151
+ if (!updateTool || !saveTool) continue;
3152
+
3153
+ for (const targetInstance of localInstances) {
3154
+ const sectionType = targetInstance?.type;
3155
+ if (!targetInstance?.id || typeof sectionType !== 'string') continue;
3156
+ const targetFieldKey = findTopLevelStringField(pageContract.sectionSchemas?.[sectionType]);
3157
+ if (!targetFieldKey) continue;
3158
+ const pageState = await readPageJson(pageEntry.slug);
3159
+ const section = Array.isArray(pageState.json.sections)
3160
+ ? pageState.json.sections.find((item) => item?.id === targetInstance.id)
3161
+ : null;
3162
+ const originalValue = section?.data?.[targetFieldKey];
3163
+ if (typeof originalValue !== 'string') continue;
3164
+
3165
+ return {
3166
+ slug: pageEntry.slug,
3167
+ manifestHref: pageEntry.manifestHref,
3168
+ contractHref: pageEntry.contractHref,
3169
+ toolName: UPDATE_TOOL_NAME,
3170
+ saveToolName: SAVE_TOOL_NAME,
3171
+ sectionId: targetInstance.id,
3172
+ sectionType,
3173
+ fieldKey: targetFieldKey,
3174
+ originalValue,
3175
+ originalState: pageState,
3176
+ };
3177
+ }
3178
+ }
3179
+
3180
+ throw new Error(
3181
+ requestedSlug
3182
+ ? `No valid WebMCP verification target found for page "${requestedSlug}".`
3183
+ : 'No valid WebMCP verification target found in manifest index.'
3184
+ );
3185
+ }
3186
+
3187
+ async function main() {
3188
+ const { chromium } = await loadPlaywright();
3189
+ const target = await selectTarget();
3190
+ const nextValue = `${target.originalValue} WebMCP ${Date.now()}`;
3191
+ const browser = await chromium.launch({ headless: true });
3192
+ const page = await browser.newPage();
3193
+ const consoleEvents = [];
3194
+ let mutationApplied = false;
3195
+
3196
+ page.on('console', (message) => {
3197
+ if (message.type() === 'error' || message.type() === 'warning') {
3198
+ consoleEvents.push(`[console:${message.type()}] ${message.text()}`);
3199
+ }
3200
+ });
3201
+ page.on('pageerror', (error) => {
3202
+ consoleEvents.push(`[pageerror] ${error.message}`);
3203
+ });
3204
+
3205
+ /** update-section mutates the Studio draft only; save persists. Returns raw update result. */
3206
+ const runUpdateThenSave = async (value) => {
3207
+ const rawUpdate = await page.evaluate(
3208
+ async ({ toolName, slug, sectionId, sectionType, fieldKey, value: nextValue }) => {
3209
+ const runtime = document.modelContextProtocol;
3210
+ if (!runtime?.executeTool) {
3211
+ throw new Error('document.modelContextProtocol.executeTool is unavailable.');
3212
+ }
3213
+ return runtime.executeTool(
3214
+ toolName,
3215
+ JSON.stringify({ slug, sectionId, sectionType, fieldKey, value: nextValue })
3216
+ );
3217
+ },
3218
+ {
3219
+ toolName: target.toolName,
3220
+ slug: target.slug,
3221
+ sectionId: target.sectionId,
3222
+ sectionType: target.sectionType,
3223
+ fieldKey: target.fieldKey,
3224
+ value,
3225
+ }
3226
+ );
3227
+ const parsedUpdate = JSON.parse(rawUpdate);
3228
+ if (parsedUpdate?.isError) {
3229
+ throw new Error(`WebMCP ${target.toolName} returned an error: ${rawUpdate}`);
3230
+ }
3231
+ const rawSave = await page.evaluate(
3232
+ async ({ saveToolName }) => document.modelContextProtocol.executeTool(saveToolName, '{}'),
3233
+ { saveToolName: target.saveToolName }
3234
+ );
3235
+ const parsedSave = JSON.parse(rawSave);
3236
+ if (parsedSave?.isError) {
3237
+ throw new Error(`WebMCP ${target.saveToolName} returned an error: ${rawSave}`);
3238
+ }
3239
+ return rawUpdate;
3240
+ };
3241
+
3242
+ const restoreOriginal = async () => {
3243
+ try {
3244
+ await runUpdateThenSave(target.originalValue);
3245
+ await waitForFileFieldValue(target.slug, target.sectionId, target.fieldKey, target.originalValue);
3246
+ } catch {
3247
+ await fs.writeFile(target.originalState.pageFilePath, target.originalState.raw, 'utf8');
3248
+ }
3249
+ };
3250
+
3251
+ try {
3252
+ const pageManifest = await fetchJson(target.manifestHref, `Manifest request for ${target.slug}`);
3253
+ if (!Array.isArray(pageManifest.tools) || !pageManifest.tools.some((tool) => tool?.name === target.toolName)) {
3254
+ throw new Error(`Manifest does not expose ${target.toolName}.`);
3255
+ }
3256
+
3257
+ const pageContract = await fetchJson(target.contractHref, `Page contract request for ${target.slug}`);
3258
+ if (!Array.isArray(pageContract.tools) || !pageContract.tools.some((tool) => tool?.name === target.toolName)) {
3259
+ throw new Error(`Page contract does not expose ${target.toolName}.`);
3260
+ }
3261
+
3262
+ await page.goto(adminUrlFromSlug(target.slug), { waitUntil: 'networkidle' });
3263
+
3264
+ try {
3265
+ await page.waitForFunction(
3266
+ ({ manifestHref, contractHref }) => {
3267
+ const manifestLink = document.head.querySelector('link[rel="mcp-manifest"]');
3268
+ const contractLink = document.head.querySelector('link[rel="olon-contract"]');
3269
+ return manifestLink?.getAttribute('href') === manifestHref
3270
+ && contractLink?.getAttribute('href') === contractHref;
3271
+ },
3272
+ { manifestHref: target.manifestHref, contractHref: target.contractHref },
3273
+ { timeout: 10_000 }
3274
+ );
3275
+ } catch (error) {
3276
+ const diagnostics = await page.evaluate(() => ({
3277
+ head: document.head.innerHTML,
3278
+ bodyText: document.body.innerText,
3279
+ }));
3280
+ throw new Error(
3281
+ [
3282
+ error instanceof Error ? error.message : String(error),
3283
+ `head=${diagnostics.head}`,
3284
+ `body=${diagnostics.bodyText}`,
3285
+ ...consoleEvents,
3286
+ ].join('\n')
3287
+ );
3288
+ }
3289
+
3290
+ const toolNames = await page.evaluate(() => {
3291
+ const runtime = document.modelContextProtocol;
3292
+ return runtime?.listTools?.().map((tool) => tool.name) ?? [];
3293
+ });
3294
+ for (const required of [target.toolName, target.saveToolName]) {
3295
+ if (!toolNames.includes(required)) {
3296
+ throw new Error(`Runtime did not register ${required}. Found: ${toolNames.join(', ')}`);
3297
+ }
3298
+ }
3299
+
3300
+ mutationApplied = true;
3301
+ await runUpdateThenSave(nextValue);
3302
+ await waitForFileFieldValue(target.slug, target.sectionId, target.fieldKey, nextValue);
3303
+ await page.frameLocator('iframe').getByText(nextValue, { exact: true }).waitFor({ state: 'attached' });
3304
+
3305
+ console.log(
3306
+ JSON.stringify({
3307
+ ok: true,
3308
+ slug: target.slug,
3309
+ manifestHref: target.manifestHref,
3310
+ contractHref: target.contractHref,
3311
+ toolName: target.toolName,
3312
+ saveToolName: target.saveToolName,
3313
+ sectionId: target.sectionId,
3314
+ sectionType: target.sectionType,
3315
+ fieldKey: target.fieldKey,
3316
+ toolNames,
3317
+ })
3318
+ );
3319
+ } finally {
3320
+ if (mutationApplied) {
3321
+ await restoreOriginal();
3322
+ }
3323
+ await browser.close();
3324
+ }
3325
+ }
3326
+
3327
+ main().catch((error) => {
3328
+ console.error(error instanceof Error ? error.stack ?? error.message : String(error));
3329
+ process.exit(1);
3330
+ });
3333
3331
 
3334
3332
  END_OF_FILE_CONTENT
3335
3333
  mkdir -p "src"
@@ -4669,7 +4667,7 @@ export const FormDemoSettingsSchema = z.object({});
4669
4667
  */
4670
4668
  export const FormDemoSubmissionSchema = z.object({
4671
4669
  name: z.string().min(1).describe('Full name of the person submitting the form'),
4672
- email: z.string().email().describe('Contact email address where we will reply'),
4670
+ email: z.email().describe('Contact email address where we will reply'),
4673
4671
  message: z.string().min(1).describe('Free-form message body'),
4674
4672
  });
4675
4673