@bifocal/mcp 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bifocalClient.js +27 -2
- package/dist/index.js +91 -13
- package/package.json +6 -3
package/dist/bifocalClient.js
CHANGED
|
@@ -17,9 +17,22 @@ async function get(path) {
|
|
|
17
17
|
}
|
|
18
18
|
return response.json();
|
|
19
19
|
}
|
|
20
|
+
export async function generateSolution(projectId, insightIds, prototypeId, goal, constraints) {
|
|
21
|
+
const response = await fetch(`${API_URL}/api/projects/${projectId}/solutions/generate`, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: headers(),
|
|
24
|
+
body: JSON.stringify({ insightIds, prototypeId, goal, constraints }),
|
|
25
|
+
});
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
const error = await response.json().catch(() => ({}));
|
|
28
|
+
throw new Error(error.error || `Request failed: ${response.status}`);
|
|
29
|
+
}
|
|
30
|
+
const data = await response.json();
|
|
31
|
+
return { solution_id: data.solution_id, status: 'generating' };
|
|
32
|
+
}
|
|
20
33
|
export async function updateProject(projectId, updates) {
|
|
21
|
-
const response = await fetch(`${API_URL}/api/projects/${projectId}`, {
|
|
22
|
-
method: '
|
|
34
|
+
const response = await fetch(`${API_URL}/api/projects/${projectId}/update`, {
|
|
35
|
+
method: 'POST',
|
|
23
36
|
headers: headers(),
|
|
24
37
|
body: JSON.stringify(updates),
|
|
25
38
|
});
|
|
@@ -82,6 +95,18 @@ export async function importPrototypeUploadUrl(projectId, prototypeName, filenam
|
|
|
82
95
|
}
|
|
83
96
|
return response.json();
|
|
84
97
|
}
|
|
98
|
+
export async function addFeedbackText(projectId, prototypeId, transcript, sessionName) {
|
|
99
|
+
const response = await fetch(`${API_URL}/api/projects/${projectId}/feedback`, {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
headers: headers(),
|
|
102
|
+
body: JSON.stringify({ prototype_id: prototypeId, transcript, session_name: sessionName }),
|
|
103
|
+
});
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
const error = await response.json().catch(() => ({}));
|
|
106
|
+
throw new Error(error.error || `Request failed: ${response.status}`);
|
|
107
|
+
}
|
|
108
|
+
return response.json();
|
|
109
|
+
}
|
|
85
110
|
export async function importPrototypeConfirm(projectId, prototypeId, contextId, filename, s3Key, s3Url) {
|
|
86
111
|
const response = await fetch(`${API_URL}/api/projects/${projectId}/prototypes/import/confirm`, {
|
|
87
112
|
method: 'POST',
|
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, 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 } 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: {} } });
|
|
@@ -21,6 +21,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
21
21
|
required: ['project_id', 'prototype_name', 'zip_path'],
|
|
22
22
|
},
|
|
23
23
|
},
|
|
24
|
+
{
|
|
25
|
+
name: 'add_feedback',
|
|
26
|
+
description: 'Add feedback to a Bifocal project. Accepts plain text or a path to a PDF file. Text is submitted directly; PDFs are parsed and text extracted before submitting.',
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
project_id: { type: 'string', description: 'The ID of the project.' },
|
|
31
|
+
prototype_id: { type: 'string', description: 'The ID of the prototype the feedback is about.' },
|
|
32
|
+
source: { type: 'string', description: 'The feedback content — either plain text or a local path to a PDF file.' },
|
|
33
|
+
session_name: { type: 'string', description: 'Optional name for the feedback session.' },
|
|
34
|
+
},
|
|
35
|
+
required: ['project_id', 'prototype_id', 'source'],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
24
38
|
{
|
|
25
39
|
name: 'get_import_instructions',
|
|
26
40
|
description: 'Get platform-specific instructions for preparing a prototype zip file before importing it into Bifocal. Always call this before import_prototype.',
|
|
@@ -89,6 +103,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
89
103
|
required: ['project_id', 'insight_id'],
|
|
90
104
|
},
|
|
91
105
|
},
|
|
106
|
+
{
|
|
107
|
+
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.',
|
|
109
|
+
inputSchema: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
properties: {
|
|
112
|
+
project_id: { type: 'string', description: 'The ID of the project.' },
|
|
113
|
+
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
|
+
goal: { type: 'string', description: 'Optional goal or focus for the solution.' },
|
|
116
|
+
constraints: { type: 'string', description: 'Optional constraints to consider.' },
|
|
117
|
+
},
|
|
118
|
+
required: ['project_id', 'insight_ids', 'prototype_id'],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
92
121
|
{
|
|
93
122
|
name: 'get_solutions',
|
|
94
123
|
description: 'Get all solutions for a Bifocal project. Returns title, brief, barriers addressed, and prototype link (if one was generated) for each solution.',
|
|
@@ -167,7 +196,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
167
196
|
const zipBuffer = await readFile(zip_path);
|
|
168
197
|
const uploadResponse = await fetch(uploadResult.upload_url, {
|
|
169
198
|
method: 'PUT',
|
|
170
|
-
headers: {
|
|
199
|
+
headers: {
|
|
200
|
+
'Content-Type': 'application/zip',
|
|
201
|
+
'Content-Encoding': 'identity',
|
|
202
|
+
},
|
|
171
203
|
body: zipBuffer,
|
|
172
204
|
});
|
|
173
205
|
if (!uploadResponse.ok) {
|
|
@@ -184,23 +216,51 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
184
216
|
response.warning = uploadResult.warning;
|
|
185
217
|
return { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }] };
|
|
186
218
|
}
|
|
219
|
+
if (name === 'add_feedback') {
|
|
220
|
+
const { project_id, prototype_id, source, session_name } = args;
|
|
221
|
+
const ext = source.split('.').pop()?.toLowerCase() || '';
|
|
222
|
+
const isFilePath = source.startsWith('/') || source.startsWith('./') || source.startsWith('~');
|
|
223
|
+
if (isFilePath && ext === 'pdf') {
|
|
224
|
+
// PDF: extract text and submit as transcript
|
|
225
|
+
const { readFile } = await import('fs/promises');
|
|
226
|
+
const pdfBuffer = await readFile(source);
|
|
227
|
+
const pdfParseModule = await import('pdf-parse');
|
|
228
|
+
const pdfParse = pdfParseModule.default ?? pdfParseModule;
|
|
229
|
+
const parsed = await pdfParse(pdfBuffer);
|
|
230
|
+
const result = await addFeedbackText(project_id, prototype_id, parsed.text, session_name);
|
|
231
|
+
return { content: [{ type: 'text', text: JSON.stringify({ ...result, note: 'Text extracted from PDF.' }, null, 2) }] };
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// Plain text
|
|
235
|
+
const result = await addFeedbackText(project_id, prototype_id, source, session_name);
|
|
236
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
187
239
|
if (name === 'get_import_instructions') {
|
|
188
240
|
const { platform } = args;
|
|
189
241
|
const instructions = {
|
|
190
|
-
claudecode:
|
|
242
|
+
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.
|
|
191
243
|
|
|
192
|
-
|
|
244
|
+
Bifocal requires a Vite + React single-page app containing only the experience being tested. Follow these steps:
|
|
245
|
+
|
|
246
|
+
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.
|
|
247
|
+
|
|
248
|
+
2. Ask: "Is this project built with Next.js or Vite?"
|
|
193
249
|
|
|
194
250
|
If Next.js:
|
|
195
|
-
-
|
|
196
|
-
-
|
|
197
|
-
-
|
|
251
|
+
- Convert to a Vite + React single-page app with react-router-dom. Remove all SSR, API routes, getServerSideProps, and getStaticProps.
|
|
252
|
+
- Verify: run npm run build and fix any errors before continuing.
|
|
253
|
+
- Create a ZIP excluding node_modules, .git, dist, and any .env files or credentials.
|
|
198
254
|
|
|
199
255
|
If Vite:
|
|
200
256
|
- Ask: "Does this project use react-router-dom so that each major section has its own URL path?"
|
|
201
|
-
- If no:
|
|
202
|
-
- Verify:
|
|
203
|
-
-
|
|
257
|
+
- If no: add react-router-dom with each major section as its own route.
|
|
258
|
+
- Verify: run npm run build and fix any errors before continuing.
|
|
259
|
+
- Create a ZIP excluding node_modules, .git, dist, and any .env files or credentials.
|
|
260
|
+
|
|
261
|
+
3. Replace any API calls or environment variables with hardcoded fixture data. The prototype must work with no backend and no network requests.
|
|
262
|
+
|
|
263
|
+
4. Stub out external services — auth, feature flags, analytics, payments, real-time connections — with simple no-op replacements.`,
|
|
204
264
|
lovable: `To export from Lovable: open your project → top-right menu → Export → Download ZIP.
|
|
205
265
|
|
|
206
266
|
Do not manually zip the project files — the Export button handles cleanup automatically.`,
|
|
@@ -209,11 +269,21 @@ Do not manually zip the project files — the Export button handles cleanup auto
|
|
|
209
269
|
2. Go to your GitHub repo → click the green Code button → Download ZIP.
|
|
210
270
|
|
|
211
271
|
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.`,
|
|
212
|
-
other:
|
|
272
|
+
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.
|
|
273
|
+
|
|
274
|
+
Bifocal requires a Vite + React single-page app containing only the experience being tested.
|
|
275
|
+
|
|
276
|
+
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.
|
|
277
|
+
|
|
278
|
+
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.
|
|
213
279
|
|
|
214
|
-
|
|
280
|
+
3. Replace any API calls or environment variables with hardcoded fixture data. The prototype must work with no backend and no network requests.
|
|
215
281
|
|
|
216
|
-
|
|
282
|
+
4. Stub out external services — auth, feature flags, analytics, payments, real-time connections — with simple no-op replacements.
|
|
283
|
+
|
|
284
|
+
5. Verify: run npm run build and fix any errors before continuing.
|
|
285
|
+
|
|
286
|
+
6. Create a ZIP excluding node_modules, .git, dist, and any .env files or credentials.`,
|
|
217
287
|
};
|
|
218
288
|
const text = instructions[platform] ?? instructions.other;
|
|
219
289
|
return { content: [{ type: 'text', text }] };
|
|
@@ -271,6 +341,14 @@ Create a ZIP of the project excluding node_modules, .git, dist, and any .env fil
|
|
|
271
341
|
const quotes = await getQuotes(project_id, insight_id);
|
|
272
342
|
return { content: [{ type: 'text', text: JSON.stringify(quotes, null, 2) }] };
|
|
273
343
|
}
|
|
344
|
+
if (name === 'generate_solution') {
|
|
345
|
+
const { project_id, insight_ids, prototype_id, goal, constraints } = args;
|
|
346
|
+
const result = await generateSolution(project_id, insight_ids, prototype_id, goal, constraints);
|
|
347
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
348
|
+
...result,
|
|
349
|
+
message: 'Solution is being generated. Use get_solutions to check when it appears — typically takes 1-2 minutes.',
|
|
350
|
+
}, null, 2) }] };
|
|
351
|
+
}
|
|
274
352
|
if (name === 'get_solutions') {
|
|
275
353
|
const { project_id } = args;
|
|
276
354
|
const solutions = await getSolutions(project_id);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bifocal/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Bifocal MCP server — access projects, insights, and prototypes from Claude",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,10 +17,13 @@
|
|
|
17
17
|
"prepublishOnly": "tsc"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
21
|
+
"@types/pdf-parse": "^1.1.5",
|
|
22
|
+
"pdf-parse": "^2.4.5"
|
|
21
23
|
},
|
|
22
24
|
"devDependencies": {
|
|
23
25
|
"@types/node": "^20.0.0",
|
|
24
26
|
"typescript": "^5.3.2"
|
|
25
|
-
}
|
|
27
|
+
},
|
|
28
|
+
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
26
29
|
}
|