@bifocal/mcp 0.1.6 → 0.1.7
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/dist/index.js +87 -136
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,12 +5,39 @@ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSche
|
|
|
5
5
|
import { createProject, updateProject, listProjects, importPrototypeUploadUrl, importPrototypeConfirm, addFeedbackText, generateSolution, getInsights, getQuotes, getSolutions, getSolution, getPrototypes, getPrototype, exportPrototype, generatePrototype, updatePrototype, getContexts, createContext, createSolution, updateSolution } from './bifocalClient.js';
|
|
6
6
|
import { writeFile } from 'fs/promises';
|
|
7
7
|
import { join } from 'path';
|
|
8
|
+
async function pollUntilReady(fn, isDone, intervalMs, timeoutMs) {
|
|
9
|
+
const deadline = Date.now() + timeoutMs;
|
|
10
|
+
while (true) {
|
|
11
|
+
const result = await fn();
|
|
12
|
+
if (isDone(result))
|
|
13
|
+
return { result, timedOut: false };
|
|
14
|
+
if (Date.now() + intervalMs >= deadline)
|
|
15
|
+
return { result, timedOut: true };
|
|
16
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
8
19
|
const server = new Server({ name: 'bifocal', version: '0.1.0' }, { capabilities: { tools: {}, resources: {} } });
|
|
9
20
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
10
21
|
tools: [
|
|
11
22
|
{
|
|
12
23
|
name: 'import_prototype',
|
|
13
|
-
description:
|
|
24
|
+
description: `Import a local zip file as a prototype into a Bifocal project.
|
|
25
|
+
|
|
26
|
+
If prototype_id is provided, the zip is uploaded to update an existing prototype (e.g. after building or editing locally via the client coding agent). The existing prototype record is reused and re-processed — no new record is created.
|
|
27
|
+
|
|
28
|
+
## Preparing the zip
|
|
29
|
+
|
|
30
|
+
**Claude Code / other agents building locally:**
|
|
31
|
+
- Do not modify any files in the existing codebase. Read existing files to understand the experience, but never edit them. Create all prototype files in a new separate directory.
|
|
32
|
+
- Bifocal requires a Vite + React single-page app with react-router-dom so that each major section has its own URL path. Next.js is not supported.
|
|
33
|
+
- Replace any API calls or environment variables with hardcoded fixture data. The prototype must work with no backend and no network requests.
|
|
34
|
+
- Stub out external services — auth, feature flags, analytics, payments, real-time connections — with simple no-op replacements.
|
|
35
|
+
- Run \`npm run build\` and fix any errors before zipping.
|
|
36
|
+
- ZIP the project excluding node_modules, .git, dist, and any .env files or credentials.
|
|
37
|
+
|
|
38
|
+
**Lovable:** open your project → top-right menu → Export → Download ZIP. Do not manually zip the files.
|
|
39
|
+
|
|
40
|
+
**Figma Make:** go to Prototype Settings → GitHub, push to a GitHub repo, then download the ZIP from GitHub (Code → Download ZIP). Note: some Figma assets may not convert correctly — check asset paths if images are missing.`,
|
|
14
41
|
inputSchema: {
|
|
15
42
|
type: 'object',
|
|
16
43
|
properties: {
|
|
@@ -36,21 +63,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
36
63
|
required: ['project_id', 'prototype_id', 'source'],
|
|
37
64
|
},
|
|
38
65
|
},
|
|
39
|
-
{
|
|
40
|
-
name: 'get_import_instructions',
|
|
41
|
-
description: 'Get platform-specific instructions for preparing a prototype zip file before importing it into Bifocal. Always call this before import_prototype.',
|
|
42
|
-
inputSchema: {
|
|
43
|
-
type: 'object',
|
|
44
|
-
properties: {
|
|
45
|
-
platform: {
|
|
46
|
-
type: 'string',
|
|
47
|
-
enum: ['claudecode', 'lovable', 'figmamake', 'other'],
|
|
48
|
-
description: 'The platform the prototype was built with.',
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
required: ['platform'],
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
66
|
{
|
|
55
67
|
name: 'create_project',
|
|
56
68
|
description: 'Create a new Bifocal project. Before calling this tool, always ask the user if they have a PRD or any context to add for the project.',
|
|
@@ -106,7 +118,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
106
118
|
},
|
|
107
119
|
{
|
|
108
120
|
name: 'create_context',
|
|
109
|
-
description: 'Create or update a context for a Bifocal project. Contexts are attached to a solution to provide additional reference material (e.g. product docs, design system, brand guidelines) during generation. If a context with the same name and type already exists for the project\'s organization, it will be updated.',
|
|
121
|
+
description: 'Create or update a context for a Bifocal project. Contexts are attached to a solution to provide additional reference material (e.g. product docs, design system, brand guidelines) during generation. If a context with the same name and type already exists for the project\'s organization, it will be updated.\n\nIf you\'re not sure whether a context with this name already exists, call get_contexts first — an existing match will be silently overwritten.',
|
|
110
122
|
inputSchema: {
|
|
111
123
|
type: 'object',
|
|
112
124
|
properties: {
|
|
@@ -132,7 +144,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
132
144
|
},
|
|
133
145
|
{
|
|
134
146
|
name: 'generate_solution',
|
|
135
|
-
description: 'Generate a new solution for a project based on selected insights. Before calling this tool, always: (1) call get_insights to show the user the available insights, (2) ask the user which insights they want to prioritize, (3) ask if they have a specific goal or any constraints for the solution, (4) optionally call get_contexts and ask the user if they want to attach any context. Only call this tool once you have that input.
|
|
147
|
+
description: 'Generate a new solution for a project based on selected insights. Before calling this tool, always: (1) call get_insights to show the user the available insights, (2) ask the user which insights they want to prioritize, (3) ask if they have a specific goal or any constraints for the solution, (4) optionally call get_contexts and ask the user if they want to attach any context. Only call this tool once you have that input. Blocks until the solution is ready and returns the full detail. The base prototype is resolved automatically.',
|
|
136
148
|
inputSchema: {
|
|
137
149
|
type: 'object',
|
|
138
150
|
properties: {
|
|
@@ -147,7 +159,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
147
159
|
},
|
|
148
160
|
{
|
|
149
161
|
name: 'create_solution',
|
|
150
|
-
description: 'Directly insert a fully-specified solution into a Bifocal project. Use this when you already know exactly what the solution should be, rather than having it generated. The solution must follow the same schema as generated solutions. The base prototype is resolved automatically.\n\nBefore calling this tool you MUST:\n1. Call get_insights — insight_ids must be real IDs from that response. Never fabricate or guess insight IDs.\n2. Call
|
|
162
|
+
description: 'Directly insert a fully-specified solution into a Bifocal project. Use this when you already know exactly what the solution should be, rather than having it generated. The solution must follow the same schema as generated solutions. The base prototype is resolved automatically.\n\nBefore calling this tool you MUST:\n1. Call get_insights — insight_ids must be real IDs from that response. Never fabricate or guess insight IDs.\n2. Call get_prototypes with the base prototype_id — pages_to_modify must be real page paths that exist in the prototype sitemap. pages_to_create must be genuinely new paths not already in the sitemap. Never fabricate page paths.\n\nAll fields are required. barriers_addressed must reflect actual barriers from the selected insights. interventions must each map to a real barrier. Do not submit this tool call until every field has been populated with valid, verified data.\n\n**IMPORTANT**: Never fabricate or guess insight_ids or page paths in interventions — they must come from a real tool response. A made-up insight ID will silently link to nothing. A made-up page path will send the coding agent to a page that doesn\'t exist.\n\n<example description="A solution with one intervention and two changes">\n{\n "project_id": "proj_abc123",\n "title": "Condensed Flow with Inline Price Transparency",\n "brief": "Surfaces pricing and meal frequency options earlier in onboarding so users can evaluate value before committing. Reduces drop-off at the plan selection step.",\n "category": "flow",\n "barriers_addressed": [\n "Users don\'t understand what\'s included in the price before they reach checkout",\n "Frequency options feel buried and hard to compare"\n ],\n "interventions": [\n {\n "barrier": "Users don\'t understand what\'s included in the price before they reach checkout",\n "changes": [\n {\n "approach": "Add frequency price cards to step 13",\n "description": "Replace the single plan summary with 3 side-by-side cards showing weekly, biweekly, and monthly frequency. Each card shows: price per delivery, total weekly cost, and a \'most popular\' badge on the biweekly option. Tapping a card selects it and updates the CTA label to reflect the chosen frequency.",\n "pages_to_modify": ["/onboarding/step-13"],\n "pages_to_create": []\n },\n {\n "approach": "Add a \'What\'s included\' expandable section to step 13",\n "description": "Below the frequency cards, add a collapsed accordion labeled \'What\'s included in every box\'. Expanding it shows: number of recipes, serving sizes, packaging info, and a skip-anytime note. Default state is collapsed.",\n "pages_to_modify": ["/onboarding/step-13"],\n "pages_to_create": []\n }\n ]\n }\n ],\n "assumptions": [\n "Users can see and compare all three frequency options without scrolling on mobile"\n ],\n "open_concerns": [\n "Does surfacing price earlier increase or decrease conversion? Should be A/B tested."\n ],\n "insight_ids": ["ins_111", "ins_222"]\n}\n</example>',
|
|
151
163
|
inputSchema: {
|
|
152
164
|
type: 'object',
|
|
153
165
|
properties: {
|
|
@@ -190,7 +202,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
190
202
|
},
|
|
191
203
|
{
|
|
192
204
|
name: 'update_solution',
|
|
193
|
-
description: 'Update an existing solution in a Bifocal project. All fields are optional — only send what needs to change. The solution schema must remain valid after the update.\n\nBefore calling this tool you MUST:\n1. Call
|
|
205
|
+
description: 'Update an existing solution in a Bifocal project. All fields are optional — only send what needs to change. The solution schema must remain valid after the update.\n\nBefore calling this tool you MUST:\n1. Call get_solutions with solution_id to read the current state. Verify solution_generation_status is "completed" — do not call this tool if the solution is still generating.\n2. If updating insight_ids: call get_insights first and use only real IDs from that response. Never fabricate or guess insight IDs.\n3. If updating interventions (and therefore page paths): call get_prototypes with the base prototype_id to confirm pages_to_modify exist in the sitemap and pages_to_create are genuinely new.\n\nDo not submit partial or invalid data. Every field you include must be fully valid and complete.\n\n**IMPORTANT**: interventions is a full replacement — include every intervention, not just the ones that changed. Omitting an existing intervention will delete it. interventions is a full replacement — include all interventions, not just the ones that changed.\n\n<example description="Updating only the interventions on an existing solution">\n{\n "project_id": "proj_abc123",\n "solution_id": "sol_xyz789",\n "interventions": [\n {\n "barrier": "Users don\'t understand what\'s included in the price before they reach checkout",\n "changes": [\n {\n "approach": "Add frequency price cards to step 13",\n "description": "Replace the single plan summary with 3 side-by-side cards showing weekly, biweekly, and monthly frequency. Each card shows price per delivery and total weekly cost.",\n "pages_to_modify": ["/onboarding/step-13"],\n "pages_to_create": []\n }\n ]\n }\n ]\n}\n</example>',
|
|
194
206
|
inputSchema: {
|
|
195
207
|
type: 'object',
|
|
196
208
|
properties: {
|
|
@@ -233,30 +245,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
233
245
|
},
|
|
234
246
|
{
|
|
235
247
|
name: 'get_solutions',
|
|
236
|
-
description: 'Get
|
|
248
|
+
description: 'Get solutions for a Bifocal project. If solution_id is provided, returns full detail for that solution including all interventions and page changes. If omitted, returns a summary list of all solutions (title, brief, barriers addressed, prototype link).',
|
|
237
249
|
inputSchema: {
|
|
238
250
|
type: 'object',
|
|
239
251
|
properties: {
|
|
240
|
-
project_id: { type: 'string', description: 'The ID of the project
|
|
252
|
+
project_id: { type: 'string', description: 'The ID of the project.' },
|
|
253
|
+
solution_id: { type: 'string', description: 'Optional. If provided, returns full detail for this specific solution.' },
|
|
241
254
|
},
|
|
242
255
|
required: ['project_id'],
|
|
243
256
|
},
|
|
244
257
|
},
|
|
245
|
-
{
|
|
246
|
-
name: 'get_solution',
|
|
247
|
-
description: 'Get full detail for a specific solution including all proposed interventions and page changes. Call this when the user wants to understand what a solution proposes to change.',
|
|
248
|
-
inputSchema: {
|
|
249
|
-
type: 'object',
|
|
250
|
-
properties: {
|
|
251
|
-
project_id: { type: 'string', description: 'The ID of the project the solution belongs to.' },
|
|
252
|
-
solution_id: { type: 'string', description: 'The ID of the solution to fetch.' },
|
|
253
|
-
},
|
|
254
|
-
required: ['project_id', 'solution_id'],
|
|
255
|
-
},
|
|
256
|
-
},
|
|
257
258
|
{
|
|
258
259
|
name: 'generate_prototype',
|
|
259
|
-
description: 'Generate a prototype from a solution.\n\nIf coding_agent is "bifocal" (default): queues
|
|
260
|
+
description: 'Generate a prototype from a solution.\n\nBefore calling, make sure you understand what the solution does — its interventions, the pages it targets, and the barriers it addresses. This helps you evaluate the result and write useful edits if needed. If you haven\'t read the full detail yet, call get_solutions with the solution_id first.\n\n**IMPORTANT**: Don\'t call this while the solution is still generating — check that solution_generation_status is completed first.\n\nIf coding_agent is "bifocal" (default): queues a build and blocks until the prototype is ready, then returns the full prototype detail.\n\nIf coding_agent is "client": returns the full solution spec, base prototype ID, and org contexts so the client can build the prototype locally. After building, call import_prototype with the returned prototype_id to upload and link. If a prototype already exists for the solution, returns it immediately regardless of coding_agent.',
|
|
260
261
|
inputSchema: {
|
|
261
262
|
type: 'object',
|
|
262
263
|
properties: {
|
|
@@ -269,40 +270,30 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
269
270
|
},
|
|
270
271
|
{
|
|
271
272
|
name: 'update_prototype',
|
|
272
|
-
description: 'Edit an existing prototype.\n\nIf coding_agent is "bifocal" (default): sends the edit instruction to Bifocal\'s coding agent
|
|
273
|
+
description: 'Edit an existing prototype.\n\nYour edit instruction should reference real pages that exist in the prototype. Make sure you know the sitemap before writing it — if you\'re not sure what pages exist, call get_prototypes with the prototype_id first.\n\nIf coding_agent is "bifocal" (default): sends the edit instruction to Bifocal\'s coding agent and blocks until the prototype is ready, then returns the full prototype detail.\n\nIf coding_agent is "client": returns the edit instruction and prototype_id so the client can make the changes locally. Export the prototype first via export_prototype, apply the changes, then call import_prototype with prototype_id to upload the updated zip.',
|
|
273
274
|
inputSchema: {
|
|
274
275
|
type: 'object',
|
|
275
276
|
properties: {
|
|
277
|
+
project_id: { type: 'string', description: 'The ID of the project the prototype belongs to.' },
|
|
276
278
|
prototype_id: { type: 'string', description: 'The ID of the prototype to update.' },
|
|
277
279
|
message: { type: 'string', description: 'The edit instruction.' },
|
|
278
280
|
coding_agent: { type: 'string', enum: ['bifocal', 'client'], description: 'Who will apply the edit. "bifocal" (default) sends to Bifocal\'s agent. "client" returns the instruction for the calling agent to apply locally.' },
|
|
279
281
|
},
|
|
280
|
-
required: ['prototype_id', 'message'],
|
|
282
|
+
required: ['project_id', 'prototype_id', 'message'],
|
|
281
283
|
},
|
|
282
284
|
},
|
|
283
285
|
{
|
|
284
286
|
name: 'get_prototypes',
|
|
285
|
-
description: '
|
|
287
|
+
description: 'Get prototypes for a Bifocal project. If prototype_id is provided, returns full detail for that prototype including its sitemap and key capabilities. If omitted, returns a summary list of all prototypes (name, status, published URL, parent prototype, linked solution). Use the full detail form when you need to understand what pages exist or poll for build status.',
|
|
286
288
|
inputSchema: {
|
|
287
289
|
type: 'object',
|
|
288
290
|
properties: {
|
|
289
|
-
project_id: { type: 'string', description: 'The ID of the project
|
|
291
|
+
project_id: { type: 'string', description: 'The ID of the project.' },
|
|
292
|
+
prototype_id: { type: 'string', description: 'Optional. If provided, returns full detail for this specific prototype.' },
|
|
290
293
|
},
|
|
291
294
|
required: ['project_id'],
|
|
292
295
|
},
|
|
293
296
|
},
|
|
294
|
-
{
|
|
295
|
-
name: 'get_prototype',
|
|
296
|
-
description: 'Get full detail for a specific prototype including its sitemap and key capabilities. Use this when you need to understand what pages exist in a prototype.',
|
|
297
|
-
inputSchema: {
|
|
298
|
-
type: 'object',
|
|
299
|
-
properties: {
|
|
300
|
-
project_id: { type: 'string', description: 'The ID of the project the prototype belongs to.' },
|
|
301
|
-
prototype_id: { type: 'string', description: 'The ID of the prototype to fetch.' },
|
|
302
|
-
},
|
|
303
|
-
required: ['project_id', 'prototype_id'],
|
|
304
|
-
},
|
|
305
|
-
},
|
|
306
297
|
{
|
|
307
298
|
name: 'export_prototype',
|
|
308
299
|
description: 'Download a prototype\'s source code as a ZIP file and save it to the local filesystem. Returns the path where the file was saved.',
|
|
@@ -375,58 +366,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
375
366
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
376
367
|
}
|
|
377
368
|
}
|
|
378
|
-
if (name === 'get_import_instructions') {
|
|
379
|
-
const { platform } = args;
|
|
380
|
-
const instructions = {
|
|
381
|
-
claudecode: `⚠️ Do not modify any files in the existing codebase. Read existing files to understand the experience, but never edit them. Create all prototype files in a new separate directory.
|
|
382
|
-
|
|
383
|
-
Bifocal requires a Vite + React single-page app containing only the experience being tested. Follow these steps:
|
|
384
|
-
|
|
385
|
-
1. Ask the user: "What specific page or flow do you want to test in Bifocal?" Only include what is needed to render that experience. Remove unrelated pages, routes, and components.
|
|
386
|
-
|
|
387
|
-
2. Ask: "Is this project built with Next.js or Vite?"
|
|
388
|
-
|
|
389
|
-
If Next.js:
|
|
390
|
-
- Convert to a Vite + React single-page app with react-router-dom. Remove all SSR, API routes, getServerSideProps, and getStaticProps.
|
|
391
|
-
- Verify: run npm run build and fix any errors before continuing.
|
|
392
|
-
- Create a ZIP excluding node_modules, .git, dist, and any .env files or credentials.
|
|
393
|
-
|
|
394
|
-
If Vite:
|
|
395
|
-
- Ask: "Does this project use react-router-dom so that each major section has its own URL path?"
|
|
396
|
-
- If no: add react-router-dom with each major section as its own route.
|
|
397
|
-
- Verify: run npm run build and fix any errors before continuing.
|
|
398
|
-
- Create a ZIP excluding node_modules, .git, dist, and any .env files or credentials.
|
|
399
|
-
|
|
400
|
-
3. Replace any API calls or environment variables with hardcoded fixture data. The prototype must work with no backend and no network requests.
|
|
401
|
-
|
|
402
|
-
4. Stub out external services — auth, feature flags, analytics, payments, real-time connections — with simple no-op replacements.`,
|
|
403
|
-
lovable: `To export from Lovable: open your project → top-right menu → Export → Download ZIP.
|
|
404
|
-
|
|
405
|
-
Do not manually zip the project files — the Export button handles cleanup automatically.`,
|
|
406
|
-
figmamake: `To export from Figma Make:
|
|
407
|
-
1. In Figma Make, go to Prototype Settings → GitHub and push your project to a GitHub repo.
|
|
408
|
-
2. Go to your GitHub repo → click the green Code button → Download ZIP.
|
|
409
|
-
|
|
410
|
-
Note: Some Figma assets may not convert correctly after export. If your build fails or images are missing, check that all asset paths are correct.`,
|
|
411
|
-
other: `⚠️ Do not modify any files in the existing codebase. Read existing files to understand the experience, but never edit them. Create all prototype files in a new separate directory.
|
|
412
|
-
|
|
413
|
-
Bifocal requires a Vite + React single-page app containing only the experience being tested.
|
|
414
|
-
|
|
415
|
-
1. Ask the user: "What specific page or flow do you want to test in Bifocal?" Only include what is needed to render that experience. Remove unrelated pages, routes, and components.
|
|
416
|
-
|
|
417
|
-
2. Your project should have package.json, index.html, and src/ at the root. Next.js is not supported — use Vite. npm run build should output to a dist/ or build/ folder.
|
|
418
|
-
|
|
419
|
-
3. Replace any API calls or environment variables with hardcoded fixture data. The prototype must work with no backend and no network requests.
|
|
420
|
-
|
|
421
|
-
4. Stub out external services — auth, feature flags, analytics, payments, real-time connections — with simple no-op replacements.
|
|
422
|
-
|
|
423
|
-
5. Verify: run npm run build and fix any errors before continuing.
|
|
424
|
-
|
|
425
|
-
6. Create a ZIP excluding node_modules, .git, dist, and any .env files or credentials.`,
|
|
426
|
-
};
|
|
427
|
-
const text = instructions[platform] ?? instructions.other;
|
|
428
|
-
return { content: [{ type: 'text', text }] };
|
|
429
|
-
}
|
|
430
369
|
if (name === 'update_project') {
|
|
431
370
|
const { project_id, name: projectName, description } = args;
|
|
432
371
|
const project = await updateProject(project_id, { name: projectName, description });
|
|
@@ -462,7 +401,7 @@ Bifocal requires a Vite + React single-page app containing only the experience b
|
|
|
462
401
|
const project = await createProject(projectName, description);
|
|
463
402
|
const response = {
|
|
464
403
|
...project,
|
|
465
|
-
next_step: "Ask the user if they have a prototype they'd like to import — projects require one to be useful. If yes, call
|
|
404
|
+
next_step: "Ask the user if they have a prototype they'd like to import — projects require one to be useful. If yes, call import_prototype — the description contains platform-specific instructions for preparing the zip.",
|
|
466
405
|
};
|
|
467
406
|
return { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }] };
|
|
468
407
|
}
|
|
@@ -497,11 +436,15 @@ Bifocal requires a Vite + React single-page app containing only the experience b
|
|
|
497
436
|
if (!basePrototype) {
|
|
498
437
|
throw new Error('No base prototype found for this project. A ready prototype with no parent is required.');
|
|
499
438
|
}
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
439
|
+
const generated = await generateSolution(project_id, insight_ids, basePrototype.id, goal, constraints, context_ids);
|
|
440
|
+
const { result, timedOut } = await pollUntilReady(() => getSolution(project_id, generated.solution_id), s => s.solution_generation_status === 'completed', 10_000, 3 * 60_000);
|
|
441
|
+
if (timedOut) {
|
|
442
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
443
|
+
solution_id: generated.solution_id,
|
|
444
|
+
message: 'Solution is still generating after 3 minutes. Call get_solutions with solution_id to check when it\'s ready.',
|
|
445
|
+
}, null, 2) }] };
|
|
446
|
+
}
|
|
447
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
505
448
|
}
|
|
506
449
|
if (name === 'create_solution') {
|
|
507
450
|
const { project_id, title, brief, category, barriers_addressed, interventions, assumptions, open_concerns, insight_ids } = args;
|
|
@@ -517,15 +460,14 @@ Bifocal requires a Vite + React single-page app containing only the experience b
|
|
|
517
460
|
return { content: [{ type: 'text', text: JSON.stringify({ ...result, message: 'Solution updated.' }, null, 2) }] };
|
|
518
461
|
}
|
|
519
462
|
if (name === 'get_solutions') {
|
|
520
|
-
const { project_id } = args;
|
|
463
|
+
const { project_id, solution_id } = args;
|
|
464
|
+
if (solution_id) {
|
|
465
|
+
const solution = await getSolution(project_id, solution_id);
|
|
466
|
+
return { content: [{ type: 'text', text: JSON.stringify(solution, null, 2) }] };
|
|
467
|
+
}
|
|
521
468
|
const solutions = await getSolutions(project_id);
|
|
522
469
|
return { content: [{ type: 'text', text: JSON.stringify(solutions, null, 2) }] };
|
|
523
470
|
}
|
|
524
|
-
if (name === 'get_solution') {
|
|
525
|
-
const { project_id, solution_id } = args;
|
|
526
|
-
const solution = await getSolution(project_id, solution_id);
|
|
527
|
-
return { content: [{ type: 'text', text: JSON.stringify(solution, null, 2) }] };
|
|
528
|
-
}
|
|
529
471
|
if (name === 'generate_prototype') {
|
|
530
472
|
const { project_id, solution_id, coding_agent } = args;
|
|
531
473
|
const result = await generatePrototype(project_id, solution_id, coding_agent);
|
|
@@ -540,10 +482,18 @@ Bifocal requires a Vite + React single-page app containing only the experience b
|
|
|
540
482
|
],
|
|
541
483
|
}, null, 2) }] };
|
|
542
484
|
}
|
|
543
|
-
|
|
485
|
+
const bifocalResult = result;
|
|
486
|
+
const { result: prototype, timedOut } = await pollUntilReady(() => getPrototype(project_id, bifocalResult.prototype_id), p => p.status === 'ready', 15_000, 10 * 60_000);
|
|
487
|
+
if (timedOut) {
|
|
488
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
489
|
+
prototype_id: bifocalResult.prototype_id,
|
|
490
|
+
message: 'Prototype is still building after 10 minutes. Call get_prototypes with prototype_id to check when it\'s ready.',
|
|
491
|
+
}, null, 2) }] };
|
|
492
|
+
}
|
|
493
|
+
return { content: [{ type: 'text', text: JSON.stringify(prototype, null, 2) }] };
|
|
544
494
|
}
|
|
545
495
|
if (name === 'update_prototype') {
|
|
546
|
-
const { prototype_id, message, coding_agent } = args;
|
|
496
|
+
const { project_id, prototype_id, message, coding_agent } = args;
|
|
547
497
|
if (coding_agent === 'client') {
|
|
548
498
|
return { content: [{ type: 'text', text: JSON.stringify({
|
|
549
499
|
prototype_id,
|
|
@@ -555,22 +505,25 @@ Bifocal requires a Vite + React single-page app containing only the experience b
|
|
|
555
505
|
],
|
|
556
506
|
}, null, 2) }] };
|
|
557
507
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
508
|
+
await updatePrototype(prototype_id, message);
|
|
509
|
+
const { result: prototype, timedOut } = await pollUntilReady(() => getPrototype(project_id, prototype_id), p => p.status === 'ready', 15_000, 10 * 60_000);
|
|
510
|
+
if (timedOut) {
|
|
511
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
512
|
+
prototype_id,
|
|
513
|
+
message: 'Prototype is still building after 10 minutes. Call get_prototypes with prototype_id to check when it\'s ready.',
|
|
514
|
+
}, null, 2) }] };
|
|
515
|
+
}
|
|
516
|
+
return { content: [{ type: 'text', text: JSON.stringify(prototype, null, 2) }] };
|
|
563
517
|
}
|
|
564
518
|
if (name === 'get_prototypes') {
|
|
565
|
-
const { project_id } = args;
|
|
519
|
+
const { project_id, prototype_id } = args;
|
|
520
|
+
if (prototype_id) {
|
|
521
|
+
const prototype = await getPrototype(project_id, prototype_id);
|
|
522
|
+
return { content: [{ type: 'text', text: JSON.stringify(prototype, null, 2) }] };
|
|
523
|
+
}
|
|
566
524
|
const prototypes = await getPrototypes(project_id);
|
|
567
525
|
return { content: [{ type: 'text', text: JSON.stringify(prototypes, null, 2) }] };
|
|
568
526
|
}
|
|
569
|
-
if (name === 'get_prototype') {
|
|
570
|
-
const { project_id, prototype_id } = args;
|
|
571
|
-
const prototype = await getPrototype(project_id, prototype_id);
|
|
572
|
-
return { content: [{ type: 'text', text: JSON.stringify(prototype, null, 2) }] };
|
|
573
|
-
}
|
|
574
527
|
if (name === 'export_prototype') {
|
|
575
528
|
const { project_id, prototype_id, output_dir } = args;
|
|
576
529
|
const { downloadUrl, fileName } = await exportPrototype(project_id, prototype_id);
|
|
@@ -635,9 +588,9 @@ Project
|
|
|
635
588
|
|
|
636
589
|
2. **Plan** — select 1–3 insights to address. Consider the user's goal and any constraints. Optionally call \`get_contexts\` to understand design constraints.
|
|
637
590
|
|
|
638
|
-
3. **Generate a solution** — call \`generate_solution\` with the selected insight IDs, a goal, and any constraints.
|
|
591
|
+
3. **Generate a solution** — call \`generate_solution\` with the selected insight IDs, a goal, and any constraints. Blocks until ready and returns the full solution detail — typically 1–2 minutes.
|
|
639
592
|
|
|
640
|
-
4. **Build a prototype** — call \`generate_prototype\` with the solution ID.
|
|
593
|
+
4. **Build a prototype** — call \`generate_prototype\` with the solution ID. Blocks until the prototype is ready and returns the full detail including \`published_url\` — typically 3–5 minutes.
|
|
641
594
|
|
|
642
595
|
5. **Iterate** — use \`update_prototype\` to refine a prototype with specific edit instructions, or generate a new solution from different insights. Use \`add_feedback\` to log what you learn from testing.
|
|
643
596
|
|
|
@@ -656,25 +609,23 @@ Project
|
|
|
656
609
|
|
|
657
610
|
**Solutions**
|
|
658
611
|
- \`generate_solution\` — async; requires insight IDs, optional goal + constraints; always elicit these from the user first
|
|
659
|
-
- \`get_solutions\` — list all solutions
|
|
660
|
-
- \`get_solution\` — full detail including interventions and page changes; always call after generation
|
|
612
|
+
- \`get_solutions\` — list all solutions, or pass solution_id for full detail including interventions and page changes
|
|
661
613
|
- \`create_solution\` — manually specify a solution without using the generator
|
|
662
614
|
- \`update_solution\` — modify an existing solution
|
|
663
615
|
|
|
664
616
|
**Prototypes**
|
|
665
617
|
- \`generate_prototype\` — async; builds from a solution; use \`coding_agent: "bifocal"\` (default) or \`"client"\` (to build locally)
|
|
666
|
-
- \`
|
|
667
|
-
- \`get_prototypes\` — list all prototypes for a project
|
|
618
|
+
- \`get_prototypes\` — list all prototypes, or pass prototype_id for full detail including sitemap; use to poll for ready status
|
|
668
619
|
- \`update_prototype\` — async edit instruction to an existing prototype; use for targeted refinements
|
|
669
620
|
- \`export_prototype\` — download source as ZIP for local editing
|
|
670
|
-
- \`import_prototype\` — upload a locally built or edited ZIP;
|
|
621
|
+
- \`import_prototype\` — upload a locally built or edited ZIP; includes platform-specific instructions
|
|
671
622
|
|
|
672
623
|
**Feedback**
|
|
673
624
|
- \`add_feedback\` — add text or PDF research to a project, linked to a prototype
|
|
674
625
|
|
|
675
626
|
## Key Behaviors to Know
|
|
676
627
|
|
|
677
|
-
- **
|
|
628
|
+
- **Blocking operations**: \`generate_solution\`, \`generate_prototype\`, and \`update_prototype\` block until the result is ready and return the full detail directly — no polling needed. If they time out, they return a message with the ID so you can check status manually via \`get_solutions\` or \`get_prototypes\`.
|
|
678
629
|
|
|
679
630
|
- **Base prototype**: Every project has a base prototype that all solution prototypes fork from. The base prototype ID is resolved automatically by \`generate_prototype\` — you do not need to specify it.
|
|
680
631
|
|