@vibesharingapp/mcp-server 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +677 -286
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,6 +4,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
5
5
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
6
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const os_1 = require("os");
|
|
7
10
|
// VibeSharing API client
|
|
8
11
|
class VibesharingClient {
|
|
9
12
|
baseUrl;
|
|
@@ -44,10 +47,10 @@ class VibesharingClient {
|
|
|
44
47
|
body: JSON.stringify(params),
|
|
45
48
|
});
|
|
46
49
|
}
|
|
47
|
-
async deployFiles(prototypeId, files, commitMessage) {
|
|
50
|
+
async deployFiles(prototypeId, files, commitMessage, deployName) {
|
|
48
51
|
return this.request(`/api/prototypes/${prototypeId}/deploy-code`, {
|
|
49
52
|
method: "POST",
|
|
50
|
-
body: JSON.stringify({ files, commitMessage }),
|
|
53
|
+
body: JSON.stringify({ files, commitMessage, deployName }),
|
|
51
54
|
});
|
|
52
55
|
}
|
|
53
56
|
async listPrototypes() {
|
|
@@ -101,10 +104,10 @@ class VibesharingClient {
|
|
|
101
104
|
}),
|
|
102
105
|
});
|
|
103
106
|
}
|
|
104
|
-
async importRepo(prototypeId, repoUrl) {
|
|
107
|
+
async importRepo(prototypeId, repoUrl, deployName) {
|
|
105
108
|
return this.request("/api/git/import-repo", {
|
|
106
109
|
method: "POST",
|
|
107
|
-
body: JSON.stringify({ prototypeId, repoUrl }),
|
|
110
|
+
body: JSON.stringify({ prototypeId, repoUrl, deployName }),
|
|
108
111
|
});
|
|
109
112
|
}
|
|
110
113
|
async addContextLink(params) {
|
|
@@ -131,6 +134,102 @@ class VibesharingClient {
|
|
|
131
134
|
});
|
|
132
135
|
}
|
|
133
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Simple fuzzy text matching. Scores based on:
|
|
139
|
+
* - Exact match → 1.0
|
|
140
|
+
* - Case-insensitive exact → 0.95
|
|
141
|
+
* - Query is a substring → 0.7–0.9 (bonus for matching at word boundaries)
|
|
142
|
+
* - Token overlap (words in common) → 0.3–0.7
|
|
143
|
+
* - Otherwise → 0
|
|
144
|
+
*/
|
|
145
|
+
function fuzzyScore(query, target) {
|
|
146
|
+
const q = query.toLowerCase().trim();
|
|
147
|
+
const t = target.toLowerCase().trim();
|
|
148
|
+
if (q === t)
|
|
149
|
+
return 1.0;
|
|
150
|
+
if (t === q)
|
|
151
|
+
return 0.95;
|
|
152
|
+
// Substring match
|
|
153
|
+
if (t.includes(q)) {
|
|
154
|
+
// Bonus if it matches at a word boundary
|
|
155
|
+
const wordBoundary = t.startsWith(q) || t.includes(` ${q}`);
|
|
156
|
+
return wordBoundary ? 0.9 : 0.75;
|
|
157
|
+
}
|
|
158
|
+
if (q.includes(t))
|
|
159
|
+
return 0.7;
|
|
160
|
+
// Token overlap
|
|
161
|
+
const qTokens = q.split(/[\s\-_]+/).filter(Boolean);
|
|
162
|
+
const tTokens = t.split(/[\s\-_]+/).filter(Boolean);
|
|
163
|
+
if (qTokens.length === 0 || tTokens.length === 0)
|
|
164
|
+
return 0;
|
|
165
|
+
let matches = 0;
|
|
166
|
+
for (const qt of qTokens) {
|
|
167
|
+
if (tTokens.some((tt) => tt.includes(qt) || qt.includes(tt))) {
|
|
168
|
+
matches++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const overlap = matches / Math.max(qTokens.length, tTokens.length);
|
|
172
|
+
return overlap * 0.7;
|
|
173
|
+
}
|
|
174
|
+
function fuzzyMatch(query, items, getName, threshold = 0.3) {
|
|
175
|
+
return items
|
|
176
|
+
.map((item) => ({ item, score: fuzzyScore(query, getName(item)) }))
|
|
177
|
+
.filter((m) => m.score >= threshold)
|
|
178
|
+
.sort((a, b) => b.score - a.score);
|
|
179
|
+
}
|
|
180
|
+
// ---- Version tracking & What's New ----
|
|
181
|
+
const CURRENT_VERSION = "0.4.1";
|
|
182
|
+
const WHATS_NEW = {
|
|
183
|
+
"0.4.0": [
|
|
184
|
+
"🆕 VibeSharing MCP v0.4.0 — What's New:",
|
|
185
|
+
"",
|
|
186
|
+
"• resolve_target tool — Call this before deploying to confirm the collection,",
|
|
187
|
+
" project name, and deploy URL with the user. Fuzzy-matches names so you don't",
|
|
188
|
+
" need exact IDs.",
|
|
189
|
+
"• Named deployments — Use deploy_name on import_repo or deploy_files to set a",
|
|
190
|
+
" friendly Vercel URL (e.g., 'erg-v3-teams' → erg-v3-teams.vercel.app).",
|
|
191
|
+
"• Fuzzy search — list_collections and list_prototypes now accept a 'search'",
|
|
192
|
+
" parameter to filter results.",
|
|
193
|
+
"• Guardrails — Deploy tools now ask for confirmation when collection or project",
|
|
194
|
+
" info is missing, instead of auto-generating everything silently.",
|
|
195
|
+
].join("\n"),
|
|
196
|
+
};
|
|
197
|
+
function getWhatsNew() {
|
|
198
|
+
try {
|
|
199
|
+
const configDir = (0, path_1.join)((0, os_1.homedir)(), ".vibesharing");
|
|
200
|
+
const versionFile = (0, path_1.join)(configDir, "mcp-version");
|
|
201
|
+
let lastVersion = null;
|
|
202
|
+
try {
|
|
203
|
+
lastVersion = (0, fs_1.readFileSync)(versionFile, "utf-8").trim();
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// First run or file missing
|
|
207
|
+
}
|
|
208
|
+
// Update stored version
|
|
209
|
+
try {
|
|
210
|
+
(0, fs_1.mkdirSync)(configDir, { recursive: true });
|
|
211
|
+
(0, fs_1.writeFileSync)(versionFile, CURRENT_VERSION);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// Non-fatal
|
|
215
|
+
}
|
|
216
|
+
if (lastVersion === CURRENT_VERSION)
|
|
217
|
+
return null;
|
|
218
|
+
// Collect all changelogs newer than lastVersion
|
|
219
|
+
const notes = [];
|
|
220
|
+
for (const [ver, note] of Object.entries(WHATS_NEW)) {
|
|
221
|
+
if (!lastVersion || ver > lastVersion) {
|
|
222
|
+
notes.push(note);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return notes.length > 0 ? notes.join("\n\n") : null;
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Check once at startup, prepend to first tool call
|
|
232
|
+
let pendingWhatsNew = getWhatsNew();
|
|
134
233
|
// Get configuration from environment
|
|
135
234
|
const VIBESHARING_URL = process.env.VIBESHARING_URL || "https://vibesharing.app";
|
|
136
235
|
const VIBESHARING_TOKEN = process.env.VIBESHARING_TOKEN;
|
|
@@ -143,7 +242,7 @@ const client = new VibesharingClient(VIBESHARING_URL, VIBESHARING_TOKEN);
|
|
|
143
242
|
// Create MCP server
|
|
144
243
|
const server = new index_js_1.Server({
|
|
145
244
|
name: "vibesharing",
|
|
146
|
-
version: "0.
|
|
245
|
+
version: "0.4.0",
|
|
147
246
|
}, {
|
|
148
247
|
capabilities: {
|
|
149
248
|
tools: {},
|
|
@@ -156,7 +255,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
156
255
|
tools: [
|
|
157
256
|
{
|
|
158
257
|
name: "register_prototype",
|
|
159
|
-
description: "Register a new prototype on VibeSharing.
|
|
258
|
+
description: "Register a new prototype on VibeSharing. IMPORTANT: Before calling this, use resolve_target to confirm the collection and project name with the user. Do not auto-generate names without user confirmation. Returns the VibeSharing URL where the team can view and leave feedback.",
|
|
160
259
|
inputSchema: {
|
|
161
260
|
type: "object",
|
|
162
261
|
properties: {
|
|
@@ -194,18 +293,28 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
194
293
|
},
|
|
195
294
|
{
|
|
196
295
|
name: "list_prototypes",
|
|
197
|
-
description: "List all prototypes in your VibeSharing organization. Shows name, URL, and recent activity.",
|
|
296
|
+
description: "List all prototypes in your VibeSharing organization. Shows name, URL, and recent activity. Optionally filter by search query (fuzzy matched).",
|
|
198
297
|
inputSchema: {
|
|
199
298
|
type: "object",
|
|
200
|
-
properties: {
|
|
299
|
+
properties: {
|
|
300
|
+
search: {
|
|
301
|
+
type: "string",
|
|
302
|
+
description: "Optional: fuzzy search query to filter prototypes by name (e.g., 'erg' or 'dashboard')",
|
|
303
|
+
},
|
|
304
|
+
},
|
|
201
305
|
},
|
|
202
306
|
},
|
|
203
307
|
{
|
|
204
308
|
name: "list_collections",
|
|
205
|
-
description: "List all collections (folders) in your VibeSharing organization. Use this to find the collection_id when registering prototypes.",
|
|
309
|
+
description: "List all collections (folders) in your VibeSharing organization. Use this to find the collection_id when registering prototypes. Optionally filter by search query (fuzzy matched).",
|
|
206
310
|
inputSchema: {
|
|
207
311
|
type: "object",
|
|
208
|
-
properties: {
|
|
312
|
+
properties: {
|
|
313
|
+
search: {
|
|
314
|
+
type: "string",
|
|
315
|
+
description: "Optional: fuzzy search query to filter collections by name (e.g., 'hero' or 'compliance')",
|
|
316
|
+
},
|
|
317
|
+
},
|
|
209
318
|
},
|
|
210
319
|
},
|
|
211
320
|
{
|
|
@@ -276,7 +385,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
276
385
|
},
|
|
277
386
|
{
|
|
278
387
|
name: "deploy_prototype",
|
|
279
|
-
description: "Deploy code directly to VibeSharing. This deploys your code to Vercel and registers it as a prototype in one step.
|
|
388
|
+
description: "Deploy code directly to VibeSharing. This deploys your code to Vercel and registers it as a prototype in one step. IMPORTANT: Before calling this, use resolve_target to confirm the collection, project name, and deploy name with the user. Do not deploy without user confirmation on where it should go.",
|
|
280
389
|
inputSchema: {
|
|
281
390
|
type: "object",
|
|
282
391
|
properties: {
|
|
@@ -316,7 +425,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
316
425
|
},
|
|
317
426
|
{
|
|
318
427
|
name: "deploy_files",
|
|
319
|
-
description: "Deploy a multi-file Next.js project to VibeSharing.
|
|
428
|
+
description: "Deploy a multi-file Next.js project to VibeSharing. Pushes files to GitHub, deploys to Vercel. Requires an existing prototype ID. IMPORTANT: Before calling this, use resolve_target to confirm the target prototype with the user. If the user hasn't specified where to deploy, do NOT proceed — ask first.",
|
|
320
429
|
inputSchema: {
|
|
321
430
|
type: "object",
|
|
322
431
|
properties: {
|
|
@@ -346,13 +455,17 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
346
455
|
type: "string",
|
|
347
456
|
description: "Optional: Git commit message (default: 'Deploy via MCP')",
|
|
348
457
|
},
|
|
458
|
+
deploy_name: {
|
|
459
|
+
type: "string",
|
|
460
|
+
description: "Optional: Friendly name for the Vercel project URL (e.g., 'erg-v3-teams' → erg-v3-teams.vercel.app). On redeploy, renames the Vercel project if different from current name.",
|
|
461
|
+
},
|
|
349
462
|
},
|
|
350
463
|
required: ["prototype_id", "files"],
|
|
351
464
|
},
|
|
352
465
|
},
|
|
353
466
|
{
|
|
354
467
|
name: "import_repo",
|
|
355
|
-
description: "Import an existing GitHub repo into VibeSharing. Pulls the code into a VibeSharing-hosted repo and deploys it to Vercel
|
|
468
|
+
description: "Import an existing GitHub repo into VibeSharing. Pulls the code into a VibeSharing-hosted repo and deploys it to Vercel. IMPORTANT: Before calling this, use resolve_target to confirm the collection, project name, and deploy name with the user. Do not import without user confirmation on where it should go and what it should be called.",
|
|
356
469
|
inputSchema: {
|
|
357
470
|
type: "object",
|
|
358
471
|
properties: {
|
|
@@ -380,10 +493,35 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
380
493
|
type: "string",
|
|
381
494
|
description: "Optional: Description of the prototype.",
|
|
382
495
|
},
|
|
496
|
+
deploy_name: {
|
|
497
|
+
type: "string",
|
|
498
|
+
description: "Optional: Friendly name for the Vercel project URL (e.g., 'erg-v3-teams' → erg-v3-teams.vercel.app). Lowercase, hyphens allowed, max 100 chars. Auto-derived from 'name' if omitted.",
|
|
499
|
+
},
|
|
383
500
|
},
|
|
384
501
|
required: ["repo_url"],
|
|
385
502
|
},
|
|
386
503
|
},
|
|
504
|
+
{
|
|
505
|
+
name: "resolve_target",
|
|
506
|
+
description: "CALL THIS BEFORE deploying or registering a prototype when the user hasn't provided exact IDs. Fuzzy-matches collection and project names, suggests where to put the prototype, and checks deploy_name availability. Returns structured options so you can confirm with the user before proceeding.",
|
|
507
|
+
inputSchema: {
|
|
508
|
+
type: "object",
|
|
509
|
+
properties: {
|
|
510
|
+
collection_name: {
|
|
511
|
+
type: "string",
|
|
512
|
+
description: "Approximate collection name to search for (e.g., 'hero use cases'). Fuzzy matched.",
|
|
513
|
+
},
|
|
514
|
+
project_name: {
|
|
515
|
+
type: "string",
|
|
516
|
+
description: "Approximate project/prototype name to search for (e.g., 'ERG v3'). Fuzzy matched.",
|
|
517
|
+
},
|
|
518
|
+
deploy_name: {
|
|
519
|
+
type: "string",
|
|
520
|
+
description: "Desired Vercel deploy name to check availability for (e.g., 'erg-v3-teams').",
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
},
|
|
387
525
|
{
|
|
388
526
|
name: "add_context_link",
|
|
389
527
|
description: "Attach a reference link or note to a collection or project/prototype. Use this to add links to Figma designs, PRDs, Confluence docs, or free-text notes that provide context for reviewers.",
|
|
@@ -451,367 +589,620 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
451
589
|
// Handle tool calls
|
|
452
590
|
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
453
591
|
const { name, arguments: args } = request.params;
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const
|
|
466
|
-
|
|
592
|
+
const toolResult = await (async () => {
|
|
593
|
+
try {
|
|
594
|
+
switch (name) {
|
|
595
|
+
case "register_prototype": {
|
|
596
|
+
const params = args;
|
|
597
|
+
// Guardrail: if no collection specified, bounce back with options
|
|
598
|
+
if (!params.collection_id) {
|
|
599
|
+
const [guardCollResult, guardProtoResult] = await Promise.all([
|
|
600
|
+
client.listCollections(),
|
|
601
|
+
client.listPrototypes(),
|
|
602
|
+
]);
|
|
603
|
+
const guardCollections = guardCollResult.collections || [];
|
|
604
|
+
const guardPrototypes = guardProtoResult.prototypes || [];
|
|
605
|
+
const similarProjects = fuzzyMatch(params.name, guardPrototypes, (p) => p.name).slice(0, 5);
|
|
606
|
+
const sections = [
|
|
607
|
+
`HOLD ON — confirm with the user before registering "${params.name}":\n`,
|
|
608
|
+
];
|
|
609
|
+
if (similarProjects.length > 0) {
|
|
610
|
+
sections.push(`Similar existing projects:\n` +
|
|
611
|
+
similarProjects.map((m) => {
|
|
612
|
+
const p = m.item;
|
|
613
|
+
return ` - "${p.name}" (ID: ${p.id})`;
|
|
614
|
+
}).join("\n") +
|
|
615
|
+
`\n\nAsk the user: Is this an update to one of these, or a new project?`);
|
|
616
|
+
}
|
|
617
|
+
if (guardCollections.length > 0) {
|
|
618
|
+
sections.push(`\nNo collection specified. Available collections:\n` +
|
|
619
|
+
guardCollections.map((c) => ` - ${c.name} (ID: ${c.id})`).join("\n") +
|
|
620
|
+
`\n\nAsk the user: Which collection should "${params.name}" go in?`);
|
|
621
|
+
}
|
|
622
|
+
return {
|
|
623
|
+
content: [
|
|
624
|
+
{
|
|
625
|
+
type: "text",
|
|
626
|
+
text: sections.join("\n"),
|
|
627
|
+
},
|
|
628
|
+
],
|
|
629
|
+
};
|
|
467
630
|
}
|
|
468
|
-
|
|
469
|
-
|
|
631
|
+
const { source_code, source_filename, ...registerParams } = params;
|
|
632
|
+
const result = await client.registerPrototype(registerParams);
|
|
633
|
+
const protoId = result.prototype?.id;
|
|
634
|
+
// Upload source code if provided
|
|
635
|
+
let sourceInfo = "";
|
|
636
|
+
if (source_code && protoId) {
|
|
637
|
+
try {
|
|
638
|
+
const sourceResult = await client.uploadSource(protoId, source_code, source_filename);
|
|
639
|
+
sourceInfo = `\nSource code uploaded (${sourceResult.source?.size || source_code.length} chars). Colleagues can download it from the prototype page.`;
|
|
640
|
+
}
|
|
641
|
+
catch (err) {
|
|
642
|
+
sourceInfo = `\nWarning: Source upload failed: ${err instanceof Error ? err.message : "Unknown error"}`;
|
|
643
|
+
}
|
|
470
644
|
}
|
|
471
|
-
}
|
|
472
|
-
return {
|
|
473
|
-
content: [
|
|
474
|
-
{
|
|
475
|
-
type: "text",
|
|
476
|
-
text: `Prototype registered successfully!\n\nName: ${result.prototype?.name || params.name}\nVibeSharing URL: ${VIBESHARING_URL}/dashboard/projects/${protoId}\n${params.external_url ? `Live URL: ${params.external_url}` : ""}${sourceInfo}\n\nYour team can now view and leave feedback on this prototype.`,
|
|
477
|
-
},
|
|
478
|
-
],
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
case "list_prototypes": {
|
|
482
|
-
const result = await client.listPrototypes();
|
|
483
|
-
const prototypes = result.prototypes || [];
|
|
484
|
-
if (prototypes.length === 0) {
|
|
485
645
|
return {
|
|
486
646
|
content: [
|
|
487
647
|
{
|
|
488
648
|
type: "text",
|
|
489
|
-
text:
|
|
649
|
+
text: `Prototype registered successfully!\n\nName: ${result.prototype?.name || params.name}\nVibeSharing URL: ${VIBESHARING_URL}/dashboard/projects/${protoId}\n${params.external_url ? `Live URL: ${params.external_url}` : ""}${sourceInfo}\n\nYour team can now view and leave feedback on this prototype.`,
|
|
490
650
|
},
|
|
491
651
|
],
|
|
492
652
|
};
|
|
493
653
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
.
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
654
|
+
case "list_prototypes": {
|
|
655
|
+
const { search: protoSearch } = (args || {});
|
|
656
|
+
const result = await client.listPrototypes();
|
|
657
|
+
let prototypes = result.prototypes || [];
|
|
658
|
+
if (protoSearch) {
|
|
659
|
+
const matches = fuzzyMatch(protoSearch, prototypes, (p) => p.name);
|
|
660
|
+
prototypes = matches.map((m) => m.item);
|
|
661
|
+
}
|
|
662
|
+
if (prototypes.length === 0) {
|
|
663
|
+
return {
|
|
664
|
+
content: [
|
|
665
|
+
{
|
|
666
|
+
type: "text",
|
|
667
|
+
text: protoSearch
|
|
668
|
+
? `No prototypes matching "${protoSearch}". Use list_prototypes without search to see all.`
|
|
669
|
+
: "No prototypes found. Use register_prototype to add your first one!",
|
|
670
|
+
},
|
|
671
|
+
],
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
const list = prototypes
|
|
675
|
+
.map((p) => `- ${p.name}\n ID: ${p.id}\n ${p.external_url ? `URL: ${p.external_url}\n ` : ""}Updated: ${new Date(p.updated_at).toLocaleDateString()}`)
|
|
676
|
+
.join("\n\n");
|
|
510
677
|
return {
|
|
511
678
|
content: [
|
|
512
679
|
{
|
|
513
680
|
type: "text",
|
|
514
|
-
text:
|
|
681
|
+
text: `Found ${prototypes.length} prototype(s)${protoSearch ? ` matching "${protoSearch}"` : ""}:\n\n${list}`,
|
|
515
682
|
},
|
|
516
683
|
],
|
|
517
684
|
};
|
|
518
685
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
.
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
686
|
+
case "list_collections": {
|
|
687
|
+
const { search: collSearch } = (args || {});
|
|
688
|
+
const result = await client.listCollections();
|
|
689
|
+
let collections = result.collections || [];
|
|
690
|
+
if (collSearch) {
|
|
691
|
+
const matches = fuzzyMatch(collSearch, collections, (c) => c.name);
|
|
692
|
+
collections = matches.map((m) => m.item);
|
|
693
|
+
}
|
|
694
|
+
if (collections.length === 0) {
|
|
695
|
+
return {
|
|
696
|
+
content: [
|
|
697
|
+
{
|
|
698
|
+
type: "text",
|
|
699
|
+
text: collSearch
|
|
700
|
+
? `No collections matching "${collSearch}". Use list_collections without search to see all.`
|
|
701
|
+
: "No collections found. Create one in the VibeSharing dashboard first.",
|
|
702
|
+
},
|
|
703
|
+
],
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
const list = collections
|
|
707
|
+
.map((c) => `- ${c.name}\n ID: ${c.id}${c.description ? `\n ${c.description}` : ""}`)
|
|
708
|
+
.join("\n\n");
|
|
536
709
|
return {
|
|
537
710
|
content: [
|
|
538
711
|
{
|
|
539
712
|
type: "text",
|
|
540
|
-
text:
|
|
713
|
+
text: `Found ${collections.length} collection(s)${collSearch ? ` matching "${collSearch}"` : ""}:\n\n${list}`,
|
|
541
714
|
},
|
|
542
715
|
],
|
|
543
716
|
};
|
|
544
717
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
const
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
{
|
|
569
|
-
type: "text",
|
|
570
|
-
text: `Context synced successfully to VibeSharing!\n\nProject ID: ${project_id}\nContent length: ${content.length} characters\n\nThis context will be available to team members viewing the prototype.`,
|
|
571
|
-
},
|
|
572
|
-
],
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
case "verify_token": {
|
|
576
|
-
const result = await client.verifyToken();
|
|
577
|
-
if (result.valid) {
|
|
718
|
+
case "get_feedback": {
|
|
719
|
+
const { project_id } = args;
|
|
720
|
+
const result = await client.getFeedback(project_id);
|
|
721
|
+
const feedback = result.feedback || [];
|
|
722
|
+
if (feedback.length === 0) {
|
|
723
|
+
return {
|
|
724
|
+
content: [
|
|
725
|
+
{
|
|
726
|
+
type: "text",
|
|
727
|
+
text: "No feedback yet for this prototype. Share it with your team to get their thoughts!",
|
|
728
|
+
},
|
|
729
|
+
],
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
const feedbackList = feedback
|
|
733
|
+
.map((f) => {
|
|
734
|
+
const status = f.resolved_at ? " [Resolved]" : "";
|
|
735
|
+
const replies = f.replies && f.replies.length > 0
|
|
736
|
+
? `\n Replies: ${f.replies.length}`
|
|
737
|
+
: "";
|
|
738
|
+
return `- ${f.user_name}${status}: "${f.content}"\n ${new Date(f.created_at).toLocaleDateString()}${replies}`;
|
|
739
|
+
})
|
|
740
|
+
.join("\n\n");
|
|
578
741
|
return {
|
|
579
742
|
content: [
|
|
580
743
|
{
|
|
581
744
|
type: "text",
|
|
582
|
-
text: `
|
|
745
|
+
text: `Feedback (${feedback.length} items):\n\n${feedbackList}`,
|
|
583
746
|
},
|
|
584
747
|
],
|
|
585
748
|
};
|
|
586
749
|
}
|
|
587
|
-
|
|
750
|
+
case "sync_context": {
|
|
751
|
+
const { project_id, content } = args;
|
|
752
|
+
await client.syncContext(project_id, content);
|
|
588
753
|
return {
|
|
589
754
|
content: [
|
|
590
755
|
{
|
|
591
756
|
type: "text",
|
|
592
|
-
text: `
|
|
757
|
+
text: `Context synced successfully to VibeSharing!\n\nProject ID: ${project_id}\nContent length: ${content.length} characters\n\nThis context will be available to team members viewing the prototype.`,
|
|
593
758
|
},
|
|
594
759
|
],
|
|
595
|
-
isError: true,
|
|
596
760
|
};
|
|
597
761
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
762
|
+
case "verify_token": {
|
|
763
|
+
const result = await client.verifyToken();
|
|
764
|
+
if (result.valid) {
|
|
765
|
+
return {
|
|
766
|
+
content: [
|
|
767
|
+
{
|
|
768
|
+
type: "text",
|
|
769
|
+
text: `Token is valid! Connected to ${VIBESHARING_URL}\n\nYour organization has ${result.prototypeCount} prototype(s).`,
|
|
770
|
+
},
|
|
771
|
+
],
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
return {
|
|
776
|
+
content: [
|
|
777
|
+
{
|
|
778
|
+
type: "text",
|
|
779
|
+
text: `Token is invalid: ${result.error}\n\nGet a new deploy token from VibeSharing → Dashboard → Account Settings.\nThen update your MCP config:\n claude mcp remove vibesharing\n claude mcp add vibesharing -e VIBESHARING_TOKEN=vs_YOUR_NEW_TOKEN -- node /path/to/mcp-server/dist/index.js`,
|
|
780
|
+
},
|
|
781
|
+
],
|
|
782
|
+
isError: true,
|
|
783
|
+
};
|
|
784
|
+
}
|
|
618
785
|
}
|
|
619
|
-
|
|
786
|
+
case "upload_source": {
|
|
787
|
+
const { prototype_id, source_code, filename, storage_option } = args;
|
|
788
|
+
const result = await client.uploadSource(prototype_id, source_code, filename, storage_option);
|
|
620
789
|
return {
|
|
621
790
|
content: [
|
|
622
791
|
{
|
|
623
792
|
type: "text",
|
|
624
|
-
text: "
|
|
793
|
+
text: `Source code uploaded successfully!\n\nPrototype ID: ${prototype_id}\nFilename: ${result.source?.filename || filename || "page.tsx"}\nSize: ${result.source?.size || source_code.length} characters\nStorage: ${result.source?.storage_option || storage_option || "permanent"}\n\nColleagues can download this from: ${VIBESHARING_URL}/dashboard/projects/${prototype_id}`,
|
|
625
794
|
},
|
|
626
795
|
],
|
|
627
|
-
isError: true,
|
|
628
796
|
};
|
|
629
797
|
}
|
|
630
|
-
|
|
631
|
-
code,
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
798
|
+
case "deploy_prototype": {
|
|
799
|
+
const { code, name, prototype_id } = args;
|
|
800
|
+
// Guardrail: if no existing prototype specified, bounce back with options
|
|
801
|
+
if (!prototype_id) {
|
|
802
|
+
const [guardCollResult, guardProtoResult] = await Promise.all([
|
|
803
|
+
client.listCollections(),
|
|
804
|
+
client.listPrototypes(),
|
|
805
|
+
]);
|
|
806
|
+
const guardCollections = guardCollResult.collections || [];
|
|
807
|
+
const guardPrototypes = guardProtoResult.prototypes || [];
|
|
808
|
+
const similarProjects = fuzzyMatch(name, guardPrototypes, (p) => p.name).slice(0, 5);
|
|
809
|
+
const sections = [
|
|
810
|
+
`HOLD ON — confirm with the user before deploying "${name}":\n`,
|
|
811
|
+
];
|
|
812
|
+
if (similarProjects.length > 0) {
|
|
813
|
+
sections.push(`Similar existing projects:\n` +
|
|
814
|
+
similarProjects.map((m) => {
|
|
815
|
+
const p = m.item;
|
|
816
|
+
return ` - "${p.name}" (ID: ${p.id})${p.external_url ? ` → ${p.external_url}` : ""}`;
|
|
817
|
+
}).join("\n") +
|
|
818
|
+
`\n\nAsk the user: Is this an UPDATE to one of these, or a NEW project?`);
|
|
819
|
+
}
|
|
820
|
+
if (guardCollections.length > 0) {
|
|
821
|
+
sections.push(`\nAvailable collections:\n` +
|
|
822
|
+
guardCollections.map((c) => ` - ${c.name} (ID: ${c.id})`).join("\n") +
|
|
823
|
+
`\n\nAsk the user: Which collection should this go in?`);
|
|
824
|
+
}
|
|
825
|
+
const suggestedSlug = name
|
|
826
|
+
.toLowerCase()
|
|
827
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
828
|
+
.replace(/^-+|-+$/g, "");
|
|
829
|
+
sections.push(`\nSuggested deploy name: "${suggestedSlug}" → https://${suggestedSlug}.vercel.app\n` +
|
|
830
|
+
`Ask the user: Do you want to use this name or choose a different one?`);
|
|
831
|
+
return {
|
|
832
|
+
content: [
|
|
833
|
+
{
|
|
834
|
+
type: "text",
|
|
835
|
+
text: sections.join("\n"),
|
|
836
|
+
},
|
|
837
|
+
],
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
// If no prototype_id, first register a new prototype
|
|
841
|
+
let prototypeId = prototype_id;
|
|
842
|
+
if (!prototypeId) {
|
|
843
|
+
const registered = await client.registerPrototype({ name });
|
|
844
|
+
prototypeId = registered.prototype?.id;
|
|
845
|
+
}
|
|
846
|
+
if (!prototypeId) {
|
|
847
|
+
return {
|
|
848
|
+
content: [
|
|
849
|
+
{
|
|
850
|
+
type: "text",
|
|
851
|
+
text: "Error: Could not create prototype. Please try again.",
|
|
852
|
+
},
|
|
853
|
+
],
|
|
854
|
+
isError: true,
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
const result = await client.deployPrototype({
|
|
858
|
+
code,
|
|
859
|
+
prototypeName: name,
|
|
860
|
+
prototypeId,
|
|
685
861
|
});
|
|
686
|
-
|
|
862
|
+
return {
|
|
863
|
+
content: [
|
|
864
|
+
{
|
|
865
|
+
type: "text",
|
|
866
|
+
text: `Deployed successfully!\n\nLive URL: ${result.deployedUrl}\nVibeSharing: ${VIBESHARING_URL}/dashboard/projects/${prototypeId}\n\nYour team can now view the prototype and leave feedback.${result.contextImported ? "\n\nProject context was automatically imported." : ""}`,
|
|
867
|
+
},
|
|
868
|
+
],
|
|
869
|
+
};
|
|
687
870
|
}
|
|
688
|
-
|
|
871
|
+
case "create_collection": {
|
|
872
|
+
const { name: collName, description: collDesc } = args;
|
|
873
|
+
const collResult = await client.createCollection({
|
|
874
|
+
name: collName,
|
|
875
|
+
description: collDesc,
|
|
876
|
+
});
|
|
877
|
+
const coll = collResult.collection;
|
|
689
878
|
return {
|
|
690
879
|
content: [
|
|
691
880
|
{
|
|
692
881
|
type: "text",
|
|
693
|
-
text:
|
|
882
|
+
text: `Collection created!\n\nName: ${coll.name}\nID: ${coll.id}\nSlug: ${coll.slug}\n\nYou can now use this collection_id when registering prototypes with register_prototype.`,
|
|
694
883
|
},
|
|
695
884
|
],
|
|
696
|
-
isError: true,
|
|
697
885
|
};
|
|
698
886
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
{
|
|
703
|
-
type: "text",
|
|
704
|
-
text: `Repo imported and deploying!\n\nRepo: ${importResult.repoUrl}\nSource: ${repo_url}\nLive URL: ${importResult.deployUrl}\nVibeSharing: ${VIBESHARING_URL}/dashboard/projects/${protoId}\nFiles imported: ${importResult.fileCount || "unknown"}\n\nPushes to the VibeSharing repo will auto-deploy to Vercel.`,
|
|
705
|
-
},
|
|
706
|
-
],
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
|
-
case "add_context_link": {
|
|
710
|
-
const { folder_id, project_id, title, url, note } = args;
|
|
711
|
-
if (!folder_id && !project_id) {
|
|
887
|
+
case "deploy_files": {
|
|
888
|
+
const { prototype_id: deployProtoId, files, commit_message, deploy_name: deployName } = args;
|
|
889
|
+
const deployResult = await client.deployFiles(deployProtoId, files, commit_message || "Deploy via MCP", deployName);
|
|
712
890
|
return {
|
|
713
891
|
content: [
|
|
714
892
|
{
|
|
715
893
|
type: "text",
|
|
716
|
-
text: "
|
|
894
|
+
text: `Deployed ${files.length} files!\n\n${deployResult.deployName ? `Deploy name: ${deployResult.deployName}\n` : ""}Live URL: ${deployResult.deployUrl || "Deploying..."}\nRepo: ${deployResult.repoUrl || "N/A"}\nCommit: ${deployResult.commitSha || "N/A"}\n\nVibeSharing: ${VIBESHARING_URL}/dashboard/projects/${deployProtoId}\n\nYour team can now view the prototype and leave feedback.`,
|
|
717
895
|
},
|
|
718
896
|
],
|
|
719
|
-
isError: true,
|
|
720
897
|
};
|
|
721
898
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
899
|
+
case "import_repo": {
|
|
900
|
+
const { repo_url, name: importName, prototype_id: importProtoId, collection_id: importCollectionId, parent_project_id: importParentId, description: importDesc, deploy_name: deployName, } = args;
|
|
901
|
+
// Guardrail: if no collection and no existing prototype specified, bounce back with options
|
|
902
|
+
if (!importCollectionId && !importProtoId) {
|
|
903
|
+
const [guardCollResult, guardProtoResult] = await Promise.all([
|
|
904
|
+
client.listCollections(),
|
|
905
|
+
client.listPrototypes(),
|
|
906
|
+
]);
|
|
907
|
+
const guardCollections = guardCollResult.collections || [];
|
|
908
|
+
const guardPrototypes = guardProtoResult.prototypes || [];
|
|
909
|
+
const repoName = repo_url.replace(/\.git$/, "").split("/").pop() || "imported-prototype";
|
|
910
|
+
const suggestedName = importName || repoName.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
911
|
+
// Check for similar existing prototypes
|
|
912
|
+
const similarProjects = importName
|
|
913
|
+
? fuzzyMatch(importName, guardPrototypes, (p) => p.name).slice(0, 5)
|
|
914
|
+
: [];
|
|
915
|
+
const sections = [
|
|
916
|
+
`HOLD ON — confirm these details with the user before deploying:\n`,
|
|
917
|
+
`Prototype name: "${suggestedName}"`,
|
|
918
|
+
];
|
|
919
|
+
if (similarProjects.length > 0) {
|
|
920
|
+
sections.push(`\nSimilar existing projects found:\n` +
|
|
921
|
+
similarProjects.map((m) => {
|
|
922
|
+
const p = m.item;
|
|
923
|
+
return ` - "${p.name}" (ID: ${p.id})${p.external_url ? ` → ${p.external_url}` : ""}`;
|
|
924
|
+
}).join("\n") +
|
|
925
|
+
`\n\nAsk the user: Is this an UPDATE to one of these existing projects, or a NEW project?`);
|
|
926
|
+
}
|
|
927
|
+
if (guardCollections.length > 0) {
|
|
928
|
+
sections.push(`\nNo collection specified. Available collections:\n` +
|
|
929
|
+
guardCollections.map((c) => ` - ${c.name} (ID: ${c.id})`).join("\n") +
|
|
930
|
+
`\n\nAsk the user: Which collection should "${suggestedName}" go in?`);
|
|
931
|
+
}
|
|
932
|
+
if (!deployName) {
|
|
933
|
+
const suggestedSlug = suggestedName
|
|
934
|
+
.toLowerCase()
|
|
935
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
936
|
+
.replace(/^-+|-+$/g, "");
|
|
937
|
+
sections.push(`\nNo deploy name specified. Suggested: "${suggestedSlug}" → https://${suggestedSlug}.vercel.app\n` +
|
|
938
|
+
`Ask the user: Do you want to use "${suggestedSlug}" or choose a different deploy name?`);
|
|
939
|
+
}
|
|
940
|
+
return {
|
|
941
|
+
content: [
|
|
942
|
+
{
|
|
943
|
+
type: "text",
|
|
944
|
+
text: sections.join("\n"),
|
|
945
|
+
},
|
|
946
|
+
],
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
// Auto-create prototype if no ID provided (collection was confirmed above)
|
|
950
|
+
let protoId = importProtoId;
|
|
951
|
+
if (!protoId) {
|
|
952
|
+
// Derive name from repo URL if not provided
|
|
953
|
+
const repoName = repo_url.replace(/\.git$/, "").split("/").pop() || "imported-prototype";
|
|
954
|
+
const protoName = importName || repoName.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
955
|
+
const registered = await client.registerPrototype({
|
|
956
|
+
name: protoName,
|
|
957
|
+
description: importDesc || `Imported from ${repo_url}`,
|
|
958
|
+
external_url: repo_url,
|
|
959
|
+
collection_id: importCollectionId,
|
|
960
|
+
parent_project_id: importParentId,
|
|
961
|
+
});
|
|
962
|
+
protoId = registered.prototype?.id;
|
|
963
|
+
}
|
|
964
|
+
if (!protoId) {
|
|
965
|
+
return {
|
|
966
|
+
content: [
|
|
967
|
+
{
|
|
968
|
+
type: "text",
|
|
969
|
+
text: "Error: Could not create prototype. Please try again.",
|
|
970
|
+
},
|
|
971
|
+
],
|
|
972
|
+
isError: true,
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
const importResult = await client.importRepo(protoId, repo_url, deployName);
|
|
743
976
|
return {
|
|
744
977
|
content: [
|
|
745
978
|
{
|
|
746
979
|
type: "text",
|
|
747
|
-
text: "
|
|
980
|
+
text: `Repo imported and deploying!\n\n${importResult.deployName ? `Deploy name: ${importResult.deployName}\n` : ""}Live URL: ${importResult.deployUrl}\nRepo: ${importResult.repoUrl}\nVibeSharing: ${VIBESHARING_URL}/dashboard/projects/${protoId}\nFiles imported: ${importResult.fileCount || "unknown"}\n\nPushes to the VibeSharing repo will auto-deploy to Vercel.`,
|
|
748
981
|
},
|
|
749
982
|
],
|
|
750
|
-
isError: true,
|
|
751
983
|
};
|
|
752
984
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
985
|
+
case "resolve_target": {
|
|
986
|
+
const { collection_name: collName, project_name: projName, deploy_name: desiredDeployName, } = (args || {});
|
|
987
|
+
// Fetch collections and prototypes in parallel
|
|
988
|
+
const [collResult, protoResult] = await Promise.all([
|
|
989
|
+
client.listCollections(),
|
|
990
|
+
client.listPrototypes(),
|
|
991
|
+
]);
|
|
992
|
+
const allCollections = collResult.collections || [];
|
|
993
|
+
const allPrototypes = protoResult.prototypes || [];
|
|
994
|
+
const sections = [];
|
|
995
|
+
// --- Collection matching ---
|
|
996
|
+
if (collName) {
|
|
997
|
+
const collMatches = fuzzyMatch(collName, allCollections, (c) => c.name);
|
|
998
|
+
if (collMatches.length === 0) {
|
|
999
|
+
sections.push(`COLLECTION: No match for "${collName}".\n` +
|
|
1000
|
+
`Available collections:\n` +
|
|
1001
|
+
allCollections
|
|
1002
|
+
.map((c) => ` - ${c.name} (ID: ${c.id})`)
|
|
1003
|
+
.join("\n") +
|
|
1004
|
+
`\n\nAsk the user: Which collection should this go in? Or create a new one?`);
|
|
1005
|
+
}
|
|
1006
|
+
else if (collMatches[0].score >= 0.9) {
|
|
1007
|
+
const best = collMatches[0].item;
|
|
1008
|
+
sections.push(`COLLECTION: Matched "${collName}" → "${best.name}" (ID: ${best.id})`);
|
|
1009
|
+
// Show prototypes in this collection for context
|
|
1010
|
+
const collProtos = allPrototypes.filter((p) => p.folder_id === best.id);
|
|
1011
|
+
if (collProtos.length > 0) {
|
|
1012
|
+
sections.push(`Existing projects in "${best.name}":\n` +
|
|
1013
|
+
collProtos
|
|
1014
|
+
.map((p) => ` - ${p.name} (ID: ${p.id})${p.external_url ? ` → ${p.external_url}` : ""}`)
|
|
1015
|
+
.join("\n") +
|
|
1016
|
+
`\n\nAsk the user: Should this be a NEW project in "${best.name}", or an update to one of these existing projects?`);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
// Fuzzy matches but not confident
|
|
1021
|
+
sections.push(`COLLECTION: "${collName}" is not an exact match. Did the user mean one of these?\n` +
|
|
1022
|
+
collMatches
|
|
1023
|
+
.slice(0, 5)
|
|
1024
|
+
.map((m) => {
|
|
1025
|
+
const c = m.item;
|
|
1026
|
+
return ` - "${c.name}" (ID: ${c.id}, confidence: ${Math.round(m.score * 100)}%)`;
|
|
1027
|
+
})
|
|
1028
|
+
.join("\n") +
|
|
1029
|
+
`\n\nAsk the user to confirm which collection.`);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
else {
|
|
1033
|
+
// No collection name given — show all options
|
|
1034
|
+
if (allCollections.length > 0) {
|
|
1035
|
+
sections.push(`COLLECTION: Not specified. Available collections:\n` +
|
|
1036
|
+
allCollections
|
|
1037
|
+
.map((c) => ` - ${c.name} (ID: ${c.id})`)
|
|
1038
|
+
.join("\n") +
|
|
1039
|
+
`\n\nAsk the user: Which collection should this prototype go in? Or create a new one?`);
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
sections.push(`COLLECTION: No collections exist yet. Ask the user if they want to create one.`);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
// --- Project matching ---
|
|
1046
|
+
if (projName) {
|
|
1047
|
+
const projMatches = fuzzyMatch(projName, allPrototypes, (p) => p.name);
|
|
1048
|
+
if (projMatches.length === 0) {
|
|
1049
|
+
sections.push(`PROJECT: No match for "${projName}". This will be a new project.\n` +
|
|
1050
|
+
`Ask the user to confirm the name for the new project.`);
|
|
1051
|
+
}
|
|
1052
|
+
else if (projMatches[0].score >= 0.9) {
|
|
1053
|
+
const best = projMatches[0].item;
|
|
1054
|
+
sections.push(`PROJECT: Matched "${projName}" → "${best.name}" (ID: ${best.id})${best.external_url ? `\n Current URL: ${best.external_url}` : ""}\n\n` +
|
|
1055
|
+
`Ask the user: Deploy as an UPDATE to "${best.name}", or create a NEW project?`);
|
|
1056
|
+
}
|
|
1057
|
+
else {
|
|
1058
|
+
sections.push(`PROJECT: "${projName}" is not an exact match. Close matches:\n` +
|
|
1059
|
+
projMatches
|
|
1060
|
+
.slice(0, 5)
|
|
1061
|
+
.map((m) => {
|
|
1062
|
+
const p = m.item;
|
|
1063
|
+
return ` - "${p.name}" (ID: ${p.id}, confidence: ${Math.round(m.score * 100)}%)`;
|
|
1064
|
+
})
|
|
1065
|
+
.join("\n") +
|
|
1066
|
+
`\n\nAsk the user: Is this an update to one of these, or a new project?`);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
// --- Deploy name ---
|
|
1070
|
+
if (desiredDeployName) {
|
|
1071
|
+
const slug = desiredDeployName
|
|
1072
|
+
.toLowerCase()
|
|
1073
|
+
.replace(/[^a-z0-9]/g, "-")
|
|
1074
|
+
.replace(/-{2,}/g, "-")
|
|
1075
|
+
.replace(/^-+|-+$/g, "");
|
|
1076
|
+
sections.push(`DEPLOY NAME: "${slug}" → will deploy to https://${slug}.vercel.app\n` +
|
|
1077
|
+
`(Availability will be checked at deploy time. If taken, a suffix will be added.)\n\n` +
|
|
1078
|
+
`Ask the user: Do you want to name this deployment "${slug}"?`);
|
|
1079
|
+
}
|
|
1080
|
+
else {
|
|
1081
|
+
sections.push(`DEPLOY NAME: Not specified.\n` +
|
|
1082
|
+
`Ask the user: Do you want a custom deploy name (e.g., "erg-v3-teams" → erg-v3-teams.vercel.app), or auto-generate one?`);
|
|
1083
|
+
}
|
|
759
1084
|
return {
|
|
760
1085
|
content: [
|
|
761
1086
|
{
|
|
762
1087
|
type: "text",
|
|
763
|
-
text:
|
|
1088
|
+
text: sections.join("\n\n---\n\n"),
|
|
764
1089
|
},
|
|
765
1090
|
],
|
|
766
1091
|
};
|
|
767
1092
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1093
|
+
case "add_context_link": {
|
|
1094
|
+
const { folder_id, project_id, title, url, note } = args;
|
|
1095
|
+
if (!folder_id && !project_id) {
|
|
1096
|
+
return {
|
|
1097
|
+
content: [
|
|
1098
|
+
{
|
|
1099
|
+
type: "text",
|
|
1100
|
+
text: "Error: Either folder_id or project_id is required.",
|
|
1101
|
+
},
|
|
1102
|
+
],
|
|
1103
|
+
isError: true,
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
const result = await client.addContextLink({
|
|
1107
|
+
folderId: folder_id,
|
|
1108
|
+
projectId: project_id,
|
|
1109
|
+
title,
|
|
1110
|
+
url,
|
|
1111
|
+
note,
|
|
1112
|
+
});
|
|
1113
|
+
const parentType = folder_id ? "collection" : "project";
|
|
1114
|
+
const parentId = folder_id || project_id;
|
|
1115
|
+
return {
|
|
1116
|
+
content: [
|
|
1117
|
+
{
|
|
1118
|
+
type: "text",
|
|
1119
|
+
text: `Reference link added successfully!\n\nTitle: ${title}${url ? `\nURL: ${url}` : ""}${note ? `\nNote: ${note}` : ""}\nAttached to ${parentType}: ${parentId}\nLink ID: ${result.link?.id}\n\nView it at: ${VIBESHARING_URL}/dashboard/${folder_id ? "folders" : "projects"}/${parentId}`,
|
|
1120
|
+
},
|
|
1121
|
+
],
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
case "list_context_links": {
|
|
1125
|
+
const { folder_id, project_id } = args;
|
|
1126
|
+
if (!folder_id && !project_id) {
|
|
1127
|
+
return {
|
|
1128
|
+
content: [
|
|
1129
|
+
{
|
|
1130
|
+
type: "text",
|
|
1131
|
+
text: "Error: Either folder_id or project_id is required.",
|
|
1132
|
+
},
|
|
1133
|
+
],
|
|
1134
|
+
isError: true,
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
const result = await client.listContextLinks({
|
|
1138
|
+
folderId: folder_id,
|
|
1139
|
+
projectId: project_id,
|
|
1140
|
+
});
|
|
1141
|
+
const links = result.links || [];
|
|
1142
|
+
if (links.length === 0) {
|
|
1143
|
+
return {
|
|
1144
|
+
content: [
|
|
1145
|
+
{
|
|
1146
|
+
type: "text",
|
|
1147
|
+
text: "No reference links found. Use add_context_link to attach Figma designs, PRDs, docs, or notes.",
|
|
1148
|
+
},
|
|
1149
|
+
],
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
const list = links
|
|
1153
|
+
.map((l) => `- ${l.title}\n ID: ${l.id}${l.url ? `\n URL: ${l.url}` : " (note)"}${l.note ? `\n Note: ${l.note}` : ""}\n Added: ${new Date(l.created_at).toLocaleDateString()}`)
|
|
1154
|
+
.join("\n\n");
|
|
1155
|
+
return {
|
|
1156
|
+
content: [
|
|
1157
|
+
{
|
|
1158
|
+
type: "text",
|
|
1159
|
+
text: `Found ${links.length} reference link(s):\n\n${list}`,
|
|
1160
|
+
},
|
|
1161
|
+
],
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
case "remove_context_link": {
|
|
1165
|
+
const { link_id } = args;
|
|
1166
|
+
await client.removeContextLink(link_id);
|
|
1167
|
+
return {
|
|
1168
|
+
content: [
|
|
1169
|
+
{
|
|
1170
|
+
type: "text",
|
|
1171
|
+
text: `Reference link ${link_id} removed successfully.`,
|
|
1172
|
+
},
|
|
1173
|
+
],
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
default:
|
|
1177
|
+
return {
|
|
1178
|
+
content: [
|
|
1179
|
+
{
|
|
1180
|
+
type: "text",
|
|
1181
|
+
text: `Unknown tool: ${name}`,
|
|
1182
|
+
},
|
|
1183
|
+
],
|
|
1184
|
+
isError: true,
|
|
1185
|
+
};
|
|
791
1186
|
}
|
|
792
|
-
default:
|
|
793
|
-
return {
|
|
794
|
-
content: [
|
|
795
|
-
{
|
|
796
|
-
type: "text",
|
|
797
|
-
text: `Unknown tool: ${name}`,
|
|
798
|
-
},
|
|
799
|
-
],
|
|
800
|
-
isError: true,
|
|
801
|
-
};
|
|
802
1187
|
}
|
|
1188
|
+
catch (error) {
|
|
1189
|
+
return {
|
|
1190
|
+
content: [
|
|
1191
|
+
{
|
|
1192
|
+
type: "text",
|
|
1193
|
+
text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1194
|
+
},
|
|
1195
|
+
],
|
|
1196
|
+
isError: true,
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
})();
|
|
1200
|
+
// Prepend What's New to the first successful tool call after upgrade
|
|
1201
|
+
if (pendingWhatsNew && !toolResult.isError) {
|
|
1202
|
+
toolResult.content.unshift({ type: "text", text: pendingWhatsNew + "\n\n---\n" });
|
|
1203
|
+
pendingWhatsNew = null;
|
|
803
1204
|
}
|
|
804
|
-
|
|
805
|
-
return {
|
|
806
|
-
content: [
|
|
807
|
-
{
|
|
808
|
-
type: "text",
|
|
809
|
-
text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
810
|
-
},
|
|
811
|
-
],
|
|
812
|
-
isError: true,
|
|
813
|
-
};
|
|
814
|
-
}
|
|
1205
|
+
return toolResult;
|
|
815
1206
|
});
|
|
816
1207
|
// List available resources (prototypes as resources)
|
|
817
1208
|
server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
|
package/package.json
CHANGED