@bifocal/mcp 0.1.3 → 0.1.5
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/bifocalClient.js +68 -4
- package/dist/index.js +213 -11
- package/package.json +1 -1
package/dist/bifocalClient.js
CHANGED
|
@@ -17,11 +17,39 @@ async function get(path) {
|
|
|
17
17
|
}
|
|
18
18
|
return response.json();
|
|
19
19
|
}
|
|
20
|
-
export async function
|
|
20
|
+
export async function getContexts(projectId) {
|
|
21
|
+
const data = await get(`/api/projects/${projectId}/contexts`);
|
|
22
|
+
return data.contexts;
|
|
23
|
+
}
|
|
24
|
+
export async function createSolution(projectId, title, brief, category, barriers_addressed, interventions, assumptions, open_concerns, insight_ids) {
|
|
25
|
+
const response = await fetch(`${API_URL}/api/projects/${projectId}/solutions`, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: headers(),
|
|
28
|
+
body: JSON.stringify({ title, brief, category, barriers_addressed, interventions, assumptions, open_concerns, insight_ids }),
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
const error = await response.json().catch(() => ({}));
|
|
32
|
+
throw new Error(error.error || `Request failed: ${response.status}`);
|
|
33
|
+
}
|
|
34
|
+
return response.json();
|
|
35
|
+
}
|
|
36
|
+
export async function createContext(projectId, name, type, content, description) {
|
|
37
|
+
const response = await fetch(`${API_URL}/api/projects/${projectId}/contexts`, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: headers(),
|
|
40
|
+
body: JSON.stringify({ name, type, content, description }),
|
|
41
|
+
});
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
const error = await response.json().catch(() => ({}));
|
|
44
|
+
throw new Error(error.error || `Request failed: ${response.status}`);
|
|
45
|
+
}
|
|
46
|
+
return response.json();
|
|
47
|
+
}
|
|
48
|
+
export async function generateSolution(projectId, insightIds, prototypeId, goal, constraints, contextIds) {
|
|
21
49
|
const response = await fetch(`${API_URL}/api/projects/${projectId}/solutions/generate`, {
|
|
22
50
|
method: 'POST',
|
|
23
51
|
headers: headers(),
|
|
24
|
-
body: JSON.stringify({ insightIds, prototypeId, goal, constraints }),
|
|
52
|
+
body: JSON.stringify({ insightIds, prototypeId, goal, constraints, contextIds }),
|
|
25
53
|
});
|
|
26
54
|
if (!response.ok) {
|
|
27
55
|
const error = await response.json().catch(() => ({}));
|
|
@@ -83,11 +111,47 @@ export async function getPrototype(projectId, prototypeId) {
|
|
|
83
111
|
export async function exportPrototype(projectId, prototypeId) {
|
|
84
112
|
return get(`/api/projects/${projectId}/prototypes/${prototypeId}/download-zip`);
|
|
85
113
|
}
|
|
86
|
-
export async function
|
|
114
|
+
export async function updateSolution(projectId, solutionId, updates) {
|
|
115
|
+
const response = await fetch(`${API_URL}/api/projects/${projectId}/solutions/${solutionId}`, {
|
|
116
|
+
method: 'PATCH',
|
|
117
|
+
headers: headers(),
|
|
118
|
+
body: JSON.stringify(updates),
|
|
119
|
+
});
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
const error = await response.json().catch(() => ({}));
|
|
122
|
+
throw new Error(error.error || `Request failed: ${response.status}`);
|
|
123
|
+
}
|
|
124
|
+
return response.json();
|
|
125
|
+
}
|
|
126
|
+
export async function updatePrototype(prototypeId, message) {
|
|
127
|
+
const response = await fetch(`${API_URL}/api/prototypes/chat`, {
|
|
128
|
+
method: 'POST',
|
|
129
|
+
headers: headers(),
|
|
130
|
+
body: JSON.stringify({ prototypeId, message }),
|
|
131
|
+
});
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
const error = await response.json().catch(() => ({}));
|
|
134
|
+
throw new Error(error.error || `Request failed: ${response.status}`);
|
|
135
|
+
}
|
|
136
|
+
return response.json();
|
|
137
|
+
}
|
|
138
|
+
export async function generatePrototype(projectId, solutionId, codingAgent) {
|
|
139
|
+
const response = await fetch(`${API_URL}/api/projects/${projectId}/solutions/${solutionId}/generate-prototype`, {
|
|
140
|
+
method: 'POST',
|
|
141
|
+
headers: headers(),
|
|
142
|
+
body: codingAgent ? JSON.stringify({ coding_agent: codingAgent }) : undefined,
|
|
143
|
+
});
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
const error = await response.json().catch(() => ({}));
|
|
146
|
+
throw new Error(error.error || `Request failed: ${response.status}`);
|
|
147
|
+
}
|
|
148
|
+
return response.json();
|
|
149
|
+
}
|
|
150
|
+
export async function importPrototypeUploadUrl(projectId, filename, prototypeName, prototypeId) {
|
|
87
151
|
const response = await fetch(`${API_URL}/api/projects/${projectId}/prototypes/import/upload-url`, {
|
|
88
152
|
method: 'POST',
|
|
89
153
|
headers: headers(),
|
|
90
|
-
body: JSON.stringify({ prototype_name: prototypeName, filename }),
|
|
154
|
+
body: JSON.stringify({ prototype_name: prototypeName, filename, prototype_id: prototypeId }),
|
|
91
155
|
});
|
|
92
156
|
if (!response.ok) {
|
|
93
157
|
const error = await response.json().catch(() => ({}));
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
-
import { createProject, updateProject, listProjects, importPrototypeUploadUrl, importPrototypeConfirm, addFeedbackText, generateSolution, getInsights, getQuotes, getSolutions, getSolution, getPrototypes, getPrototype, exportPrototype } from './bifocalClient.js';
|
|
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
8
|
const server = new Server({ name: 'bifocal', version: '0.1.0' }, { capabilities: { tools: {} } });
|
|
@@ -10,15 +10,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
10
10
|
tools: [
|
|
11
11
|
{
|
|
12
12
|
name: 'import_prototype',
|
|
13
|
-
description: 'Import a local zip file as a prototype into a Bifocal project. Always call get_import_instructions first to ensure the zip is correctly prepared.',
|
|
13
|
+
description: 'Import a local zip file as a prototype into a Bifocal project. Always call get_import_instructions first to ensure the zip is correctly prepared.\n\nIf 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.',
|
|
14
14
|
inputSchema: {
|
|
15
15
|
type: 'object',
|
|
16
16
|
properties: {
|
|
17
17
|
project_id: { type: 'string', description: 'The ID of the project to import into.' },
|
|
18
|
-
prototype_name: { type: 'string', description: 'A name for the prototype.' },
|
|
18
|
+
prototype_name: { type: 'string', description: 'A name for the prototype. Required when creating a new prototype (no prototype_id).' },
|
|
19
19
|
zip_path: { type: 'string', description: 'Absolute path to the zip file on the local filesystem.' },
|
|
20
|
+
prototype_id: { type: 'string', description: 'If provided, updates this existing prototype instead of creating a new one. Use this after building or editing a prototype locally.' },
|
|
20
21
|
},
|
|
21
|
-
required: ['project_id', '
|
|
22
|
+
required: ['project_id', 'zip_path'],
|
|
22
23
|
},
|
|
23
24
|
},
|
|
24
25
|
{
|
|
@@ -103,19 +104,131 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
103
104
|
required: ['project_id', 'insight_id'],
|
|
104
105
|
},
|
|
105
106
|
},
|
|
107
|
+
{
|
|
108
|
+
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.',
|
|
110
|
+
inputSchema: {
|
|
111
|
+
type: 'object',
|
|
112
|
+
properties: {
|
|
113
|
+
project_id: { type: 'string', description: 'The ID of the project.' },
|
|
114
|
+
name: { type: 'string', description: 'A short name for the context (e.g. "Brand Guidelines", "Design System").' },
|
|
115
|
+
type: { type: 'string', description: 'The type of context (e.g. "product", "design", "brand", "research", "other").' },
|
|
116
|
+
content: { type: 'string', description: 'The full text content of the context.' },
|
|
117
|
+
description: { type: 'string', description: 'Optional short description of what this context contains.' },
|
|
118
|
+
},
|
|
119
|
+
required: ['project_id', 'name', 'type', 'content'],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'get_contexts',
|
|
124
|
+
description: 'Get all contexts for a Bifocal project. Contexts contain product, design, or other reference material that can be attached to a solution to guide generation. Call this before generate_solution if you want to include context.',
|
|
125
|
+
inputSchema: {
|
|
126
|
+
type: 'object',
|
|
127
|
+
properties: {
|
|
128
|
+
project_id: { type: 'string', description: 'The ID of the project.' },
|
|
129
|
+
},
|
|
130
|
+
required: ['project_id'],
|
|
131
|
+
},
|
|
132
|
+
},
|
|
106
133
|
{
|
|
107
134
|
name: 'generate_solution',
|
|
108
|
-
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. Only call this tool once you have that input. This is async — use get_solutions to check when the solution appears.',
|
|
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. This is async — use get_solutions to check when the solution appears. The base prototype is resolved automatically.',
|
|
109
136
|
inputSchema: {
|
|
110
137
|
type: 'object',
|
|
111
138
|
properties: {
|
|
112
139
|
project_id: { type: 'string', description: 'The ID of the project.' },
|
|
113
140
|
insight_ids: { type: 'array', items: { type: 'string' }, description: 'IDs of insights to base the solution on. Use get_insights to find them.' },
|
|
114
|
-
prototype_id: { type: 'string', description: 'The ID of the prototype to use as context for the solution.' },
|
|
115
141
|
goal: { type: 'string', description: 'Optional goal or focus for the solution.' },
|
|
116
142
|
constraints: { type: 'string', description: 'Optional constraints to consider.' },
|
|
143
|
+
context_ids: { type: 'array', items: { type: 'string' }, description: 'Optional IDs of contexts to attach. Use get_contexts to find them.' },
|
|
144
|
+
},
|
|
145
|
+
required: ['project_id', 'insight_ids'],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
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 get_prototype on the base prototype — 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.',
|
|
151
|
+
inputSchema: {
|
|
152
|
+
type: 'object',
|
|
153
|
+
properties: {
|
|
154
|
+
project_id: { type: 'string', description: 'The ID of the project.' },
|
|
155
|
+
title: { type: 'string', description: 'Concise name for the solution (e.g. "Condensed Flow with Unified Plan Builder").' },
|
|
156
|
+
brief: { type: 'string', description: '1-2 sentence summary of what the solution does and why.' },
|
|
157
|
+
category: { type: 'string', enum: ['feature', 'design', 'copy', 'content', 'flow', 'pricing', 'simplification', 'other'], description: 'Category that best describes the type of change.' },
|
|
158
|
+
barriers_addressed: { type: 'array', items: { type: 'string' }, description: 'List of user barriers or pain points this solution directly addresses.' },
|
|
159
|
+
interventions: {
|
|
160
|
+
type: 'array',
|
|
161
|
+
description: 'One or more interventions, each addressing a specific barrier. Each intervention contains a set of concrete UI/UX changes to implement.',
|
|
162
|
+
items: {
|
|
163
|
+
type: 'object',
|
|
164
|
+
properties: {
|
|
165
|
+
barrier: { type: 'string', description: 'The specific barrier or problem this intervention targets.' },
|
|
166
|
+
changes: {
|
|
167
|
+
type: 'array',
|
|
168
|
+
description: 'Concrete changes to implement for this barrier.',
|
|
169
|
+
items: {
|
|
170
|
+
type: 'object',
|
|
171
|
+
properties: {
|
|
172
|
+
approach: { type: 'string', description: 'Short name/headline for this change (e.g. "Add frequency price cards to step 13").' },
|
|
173
|
+
description: { type: 'string', description: 'Detailed description of what to build or change, including layout, copy, interactions, and any edge cases. Be specific enough for a coding agent to implement it.' },
|
|
174
|
+
pages_to_modify: { type: 'array', items: { type: 'string' }, description: 'Existing page route paths to modify (e.g. ["/onboarding/step-13"]). Empty array if none.' },
|
|
175
|
+
pages_to_create: { type: 'array', items: { type: 'string' }, description: 'New page route paths to create (e.g. ["/menu-preview"]). Empty array if none.' },
|
|
176
|
+
},
|
|
177
|
+
required: ['approach', 'description', 'pages_to_modify', 'pages_to_create'],
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
required: ['barrier', 'changes'],
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
assumptions: { type: 'array', items: { type: 'string' }, description: 'Assumptions this solution makes about the product, users, or implementation. Use an empty array if none.' },
|
|
185
|
+
open_concerns: { type: 'array', items: { type: 'string' }, description: 'Open questions, risks, or things to validate. Use an empty array if none.' },
|
|
186
|
+
insight_ids: { type: 'array', items: { type: 'string' }, description: 'IDs of insights this solution addresses. Use get_insights to find them.' },
|
|
117
187
|
},
|
|
118
|
-
required: ['project_id', '
|
|
188
|
+
required: ['project_id', 'title', 'brief', 'category', 'barriers_addressed', 'interventions', 'assumptions', 'open_concerns', 'insight_ids'],
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
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 get_solution 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_prototype on the base prototype 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.',
|
|
194
|
+
inputSchema: {
|
|
195
|
+
type: 'object',
|
|
196
|
+
properties: {
|
|
197
|
+
project_id: { type: 'string', description: 'The ID of the project.' },
|
|
198
|
+
solution_id: { type: 'string', description: 'The ID of the solution to update.' },
|
|
199
|
+
title: { type: 'string', description: 'Updated title for the solution.' },
|
|
200
|
+
brief: { type: 'string', description: 'Updated 1-2 sentence summary.' },
|
|
201
|
+
category: { type: 'string', enum: ['feature', 'design', 'copy', 'content', 'flow', 'pricing', 'simplification', 'other'], description: 'Updated category.' },
|
|
202
|
+
barriers_addressed: { type: 'array', items: { type: 'string' }, description: 'Full replacement list of barriers addressed.' },
|
|
203
|
+
interventions: {
|
|
204
|
+
type: 'array',
|
|
205
|
+
description: 'Full replacement list of interventions. If provided, selected_changes will be re-derived automatically.',
|
|
206
|
+
items: {
|
|
207
|
+
type: 'object',
|
|
208
|
+
properties: {
|
|
209
|
+
barrier: { type: 'string', description: 'The specific barrier this intervention targets.' },
|
|
210
|
+
changes: {
|
|
211
|
+
type: 'array',
|
|
212
|
+
items: {
|
|
213
|
+
type: 'object',
|
|
214
|
+
properties: {
|
|
215
|
+
approach: { type: 'string' },
|
|
216
|
+
description: { type: 'string' },
|
|
217
|
+
pages_to_modify: { type: 'array', items: { type: 'string' } },
|
|
218
|
+
pages_to_create: { type: 'array', items: { type: 'string' } },
|
|
219
|
+
},
|
|
220
|
+
required: ['approach', 'description', 'pages_to_modify', 'pages_to_create'],
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
required: ['barrier', 'changes'],
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
assumptions: { type: 'array', items: { type: 'string' }, description: 'Full replacement list of assumptions.' },
|
|
228
|
+
open_concerns: { type: 'array', items: { type: 'string' }, description: 'Full replacement list of open concerns.' },
|
|
229
|
+
insight_ids: { type: 'array', items: { type: 'string' }, description: 'Full replacement list of insight IDs. Replaces all existing links.' },
|
|
230
|
+
},
|
|
231
|
+
required: ['project_id', 'solution_id'],
|
|
119
232
|
},
|
|
120
233
|
},
|
|
121
234
|
{
|
|
@@ -141,6 +254,32 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
141
254
|
required: ['project_id', 'solution_id'],
|
|
142
255
|
},
|
|
143
256
|
},
|
|
257
|
+
{
|
|
258
|
+
name: 'generate_prototype',
|
|
259
|
+
description: 'Generate a prototype from a solution.\n\nIf coding_agent is "bifocal" (default): queues an async build — poll get_prototype until status is "ready".\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
|
+
inputSchema: {
|
|
261
|
+
type: 'object',
|
|
262
|
+
properties: {
|
|
263
|
+
project_id: { type: 'string', description: 'The ID of the project.' },
|
|
264
|
+
solution_id: { type: 'string', description: 'The ID of the solution to generate a prototype from.' },
|
|
265
|
+
coding_agent: { type: 'string', enum: ['bifocal', 'client'], description: 'Who will build the prototype. "bifocal" (default) queues it to Bifocal\'s agent. "client" returns the spec for the calling agent to build locally.' },
|
|
266
|
+
},
|
|
267
|
+
required: ['project_id', 'solution_id'],
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
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 — async, poll get_prototype until status returns to "ready".\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
|
+
inputSchema: {
|
|
274
|
+
type: 'object',
|
|
275
|
+
properties: {
|
|
276
|
+
prototype_id: { type: 'string', description: 'The ID of the prototype to update.' },
|
|
277
|
+
message: { type: 'string', description: 'The edit instruction.' },
|
|
278
|
+
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
|
+
},
|
|
280
|
+
required: ['prototype_id', 'message'],
|
|
281
|
+
},
|
|
282
|
+
},
|
|
144
283
|
{
|
|
145
284
|
name: 'get_prototypes',
|
|
146
285
|
description: 'List all prototypes for a Bifocal project. Returns name, status, published URL, parent prototype (for iteration chains), and linked solution if any. Ordered oldest to newest.',
|
|
@@ -183,10 +322,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
183
322
|
const { name, arguments: args } = request.params;
|
|
184
323
|
try {
|
|
185
324
|
if (name === 'import_prototype') {
|
|
186
|
-
const { project_id, prototype_name, zip_path } = args;
|
|
325
|
+
const { project_id, prototype_name, zip_path, prototype_id: existingPrototypeId } = args;
|
|
187
326
|
const filename = zip_path.split('/').pop() || 'prototype.zip';
|
|
188
327
|
// Step 1: get presigned upload URL
|
|
189
|
-
const uploadResult = await importPrototypeUploadUrl(project_id, prototype_name,
|
|
328
|
+
const uploadResult = await importPrototypeUploadUrl(project_id, filename, prototype_name, existingPrototypeId);
|
|
190
329
|
if (uploadResult.warning) {
|
|
191
330
|
// Surface warning but continue
|
|
192
331
|
console.warn('[import_prototype]', uploadResult.warning);
|
|
@@ -341,14 +480,42 @@ Bifocal requires a Vite + React single-page app containing only the experience b
|
|
|
341
480
|
const quotes = await getQuotes(project_id, insight_id);
|
|
342
481
|
return { content: [{ type: 'text', text: JSON.stringify(quotes, null, 2) }] };
|
|
343
482
|
}
|
|
483
|
+
if (name === 'create_context') {
|
|
484
|
+
const { project_id, name: contextName, type, content, description } = args;
|
|
485
|
+
const result = await createContext(project_id, contextName, type, content, description);
|
|
486
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
487
|
+
}
|
|
488
|
+
if (name === 'get_contexts') {
|
|
489
|
+
const { project_id } = args;
|
|
490
|
+
const contexts = await getContexts(project_id);
|
|
491
|
+
return { content: [{ type: 'text', text: JSON.stringify(contexts, null, 2) }] };
|
|
492
|
+
}
|
|
344
493
|
if (name === 'generate_solution') {
|
|
345
|
-
const { project_id, insight_ids,
|
|
346
|
-
const
|
|
494
|
+
const { project_id, insight_ids, goal, constraints, context_ids } = args;
|
|
495
|
+
const prototypes = await getPrototypes(project_id);
|
|
496
|
+
const basePrototype = prototypes.find(p => p.parent_prototype_id === null && p.status === 'ready');
|
|
497
|
+
if (!basePrototype) {
|
|
498
|
+
throw new Error('No base prototype found for this project. A ready prototype with no parent is required.');
|
|
499
|
+
}
|
|
500
|
+
const result = await generateSolution(project_id, insight_ids, basePrototype.id, goal, constraints, context_ids);
|
|
347
501
|
return { content: [{ type: 'text', text: JSON.stringify({
|
|
348
502
|
...result,
|
|
349
503
|
message: 'Solution is being generated. Use get_solutions to check when it appears — typically takes 1-2 minutes.',
|
|
350
504
|
}, null, 2) }] };
|
|
351
505
|
}
|
|
506
|
+
if (name === 'create_solution') {
|
|
507
|
+
const { project_id, title, brief, category, barriers_addressed, interventions, assumptions, open_concerns, insight_ids } = args;
|
|
508
|
+
const result = await createSolution(project_id, title, brief, category, barriers_addressed, interventions, assumptions, open_concerns, insight_ids);
|
|
509
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
510
|
+
...result,
|
|
511
|
+
message: 'Solution created. Call generate_prototype to build a prototype from it.',
|
|
512
|
+
}, null, 2) }] };
|
|
513
|
+
}
|
|
514
|
+
if (name === 'update_solution') {
|
|
515
|
+
const { project_id, solution_id, title, brief, category, barriers_addressed, interventions, assumptions, open_concerns, insight_ids } = args;
|
|
516
|
+
const result = await updateSolution(project_id, solution_id, { title, brief, category, barriers_addressed, interventions, assumptions, open_concerns, insight_ids });
|
|
517
|
+
return { content: [{ type: 'text', text: JSON.stringify({ ...result, message: 'Solution updated.' }, null, 2) }] };
|
|
518
|
+
}
|
|
352
519
|
if (name === 'get_solutions') {
|
|
353
520
|
const { project_id } = args;
|
|
354
521
|
const solutions = await getSolutions(project_id);
|
|
@@ -359,6 +526,41 @@ Bifocal requires a Vite + React single-page app containing only the experience b
|
|
|
359
526
|
const solution = await getSolution(project_id, solution_id);
|
|
360
527
|
return { content: [{ type: 'text', text: JSON.stringify(solution, null, 2) }] };
|
|
361
528
|
}
|
|
529
|
+
if (name === 'generate_prototype') {
|
|
530
|
+
const { project_id, solution_id, coding_agent } = args;
|
|
531
|
+
const result = await generatePrototype(project_id, solution_id, coding_agent);
|
|
532
|
+
if (coding_agent === 'client') {
|
|
533
|
+
const clientResult = result;
|
|
534
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
535
|
+
...clientResult,
|
|
536
|
+
instructions: [
|
|
537
|
+
`1. Call export_prototype with project_id and base_prototype_id to download the base code.`,
|
|
538
|
+
`2. Unzip and implement the changes described in solution.interventions. Apply org contexts (design system, brand guidelines) if provided.`,
|
|
539
|
+
`3. Zip the result and call import_prototype with project_id, zip_path, and prototype_id (${clientResult.prototype_id}).`,
|
|
540
|
+
],
|
|
541
|
+
}, null, 2) }] };
|
|
542
|
+
}
|
|
543
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
544
|
+
}
|
|
545
|
+
if (name === 'update_prototype') {
|
|
546
|
+
const { prototype_id, message, coding_agent } = args;
|
|
547
|
+
if (coding_agent === 'client') {
|
|
548
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
549
|
+
prototype_id,
|
|
550
|
+
edit_instruction: message,
|
|
551
|
+
instructions: [
|
|
552
|
+
`1. Call export_prototype to download the current prototype code.`,
|
|
553
|
+
`2. Unzip and apply the edit instruction above.`,
|
|
554
|
+
`3. Zip the result and call import_prototype with project_id, zip_path, and prototype_id (${prototype_id}).`,
|
|
555
|
+
],
|
|
556
|
+
}, null, 2) }] };
|
|
557
|
+
}
|
|
558
|
+
const result = await updatePrototype(prototype_id, message);
|
|
559
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
560
|
+
...result,
|
|
561
|
+
message: 'Edit queued. Call get_prototype to poll until status returns to "ready".',
|
|
562
|
+
}, null, 2) }] };
|
|
563
|
+
}
|
|
362
564
|
if (name === 'get_prototypes') {
|
|
363
565
|
const { project_id } = args;
|
|
364
566
|
const prototypes = await getPrototypes(project_id);
|