@vibesharingapp/mcp-server 0.3.1 → 0.4.0
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 +355 -28
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -44,10 +44,10 @@ class VibesharingClient {
|
|
|
44
44
|
body: JSON.stringify(params),
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
|
-
async deployFiles(prototypeId, files, commitMessage) {
|
|
47
|
+
async deployFiles(prototypeId, files, commitMessage, deployName) {
|
|
48
48
|
return this.request(`/api/prototypes/${prototypeId}/deploy-code`, {
|
|
49
49
|
method: "POST",
|
|
50
|
-
body: JSON.stringify({ files, commitMessage }),
|
|
50
|
+
body: JSON.stringify({ files, commitMessage, deployName }),
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
async listPrototypes() {
|
|
@@ -101,10 +101,10 @@ class VibesharingClient {
|
|
|
101
101
|
}),
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
|
-
async importRepo(prototypeId, repoUrl) {
|
|
104
|
+
async importRepo(prototypeId, repoUrl, deployName) {
|
|
105
105
|
return this.request("/api/git/import-repo", {
|
|
106
106
|
method: "POST",
|
|
107
|
-
body: JSON.stringify({ prototypeId, repoUrl }),
|
|
107
|
+
body: JSON.stringify({ prototypeId, repoUrl, deployName }),
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
110
|
async addContextLink(params) {
|
|
@@ -131,8 +131,51 @@ class VibesharingClient {
|
|
|
131
131
|
});
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Simple fuzzy text matching. Scores based on:
|
|
136
|
+
* - Exact match → 1.0
|
|
137
|
+
* - Case-insensitive exact → 0.95
|
|
138
|
+
* - Query is a substring → 0.7–0.9 (bonus for matching at word boundaries)
|
|
139
|
+
* - Token overlap (words in common) → 0.3–0.7
|
|
140
|
+
* - Otherwise → 0
|
|
141
|
+
*/
|
|
142
|
+
function fuzzyScore(query, target) {
|
|
143
|
+
const q = query.toLowerCase().trim();
|
|
144
|
+
const t = target.toLowerCase().trim();
|
|
145
|
+
if (q === t)
|
|
146
|
+
return 1.0;
|
|
147
|
+
if (t === q)
|
|
148
|
+
return 0.95;
|
|
149
|
+
// Substring match
|
|
150
|
+
if (t.includes(q)) {
|
|
151
|
+
// Bonus if it matches at a word boundary
|
|
152
|
+
const wordBoundary = t.startsWith(q) || t.includes(` ${q}`);
|
|
153
|
+
return wordBoundary ? 0.9 : 0.75;
|
|
154
|
+
}
|
|
155
|
+
if (q.includes(t))
|
|
156
|
+
return 0.7;
|
|
157
|
+
// Token overlap
|
|
158
|
+
const qTokens = q.split(/[\s\-_]+/).filter(Boolean);
|
|
159
|
+
const tTokens = t.split(/[\s\-_]+/).filter(Boolean);
|
|
160
|
+
if (qTokens.length === 0 || tTokens.length === 0)
|
|
161
|
+
return 0;
|
|
162
|
+
let matches = 0;
|
|
163
|
+
for (const qt of qTokens) {
|
|
164
|
+
if (tTokens.some((tt) => tt.includes(qt) || qt.includes(tt))) {
|
|
165
|
+
matches++;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const overlap = matches / Math.max(qTokens.length, tTokens.length);
|
|
169
|
+
return overlap * 0.7;
|
|
170
|
+
}
|
|
171
|
+
function fuzzyMatch(query, items, getName, threshold = 0.3) {
|
|
172
|
+
return items
|
|
173
|
+
.map((item) => ({ item, score: fuzzyScore(query, getName(item)) }))
|
|
174
|
+
.filter((m) => m.score >= threshold)
|
|
175
|
+
.sort((a, b) => b.score - a.score);
|
|
176
|
+
}
|
|
134
177
|
// Get configuration from environment
|
|
135
|
-
const VIBESHARING_URL = process.env.VIBESHARING_URL || "https://
|
|
178
|
+
const VIBESHARING_URL = process.env.VIBESHARING_URL || "https://vibesharing.app";
|
|
136
179
|
const VIBESHARING_TOKEN = process.env.VIBESHARING_TOKEN;
|
|
137
180
|
if (!VIBESHARING_TOKEN) {
|
|
138
181
|
console.error("Error: VIBESHARING_TOKEN environment variable is required");
|
|
@@ -143,7 +186,7 @@ const client = new VibesharingClient(VIBESHARING_URL, VIBESHARING_TOKEN);
|
|
|
143
186
|
// Create MCP server
|
|
144
187
|
const server = new index_js_1.Server({
|
|
145
188
|
name: "vibesharing",
|
|
146
|
-
version: "0.
|
|
189
|
+
version: "0.4.0",
|
|
147
190
|
}, {
|
|
148
191
|
capabilities: {
|
|
149
192
|
tools: {},
|
|
@@ -156,7 +199,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
156
199
|
tools: [
|
|
157
200
|
{
|
|
158
201
|
name: "register_prototype",
|
|
159
|
-
description: "Register a new prototype on VibeSharing.
|
|
202
|
+
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
203
|
inputSchema: {
|
|
161
204
|
type: "object",
|
|
162
205
|
properties: {
|
|
@@ -194,18 +237,28 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
194
237
|
},
|
|
195
238
|
{
|
|
196
239
|
name: "list_prototypes",
|
|
197
|
-
description: "List all prototypes in your VibeSharing organization. Shows name, URL, and recent activity.",
|
|
240
|
+
description: "List all prototypes in your VibeSharing organization. Shows name, URL, and recent activity. Optionally filter by search query (fuzzy matched).",
|
|
198
241
|
inputSchema: {
|
|
199
242
|
type: "object",
|
|
200
|
-
properties: {
|
|
243
|
+
properties: {
|
|
244
|
+
search: {
|
|
245
|
+
type: "string",
|
|
246
|
+
description: "Optional: fuzzy search query to filter prototypes by name (e.g., 'erg' or 'dashboard')",
|
|
247
|
+
},
|
|
248
|
+
},
|
|
201
249
|
},
|
|
202
250
|
},
|
|
203
251
|
{
|
|
204
252
|
name: "list_collections",
|
|
205
|
-
description: "List all collections (folders) in your VibeSharing organization. Use this to find the collection_id when registering prototypes.",
|
|
253
|
+
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
254
|
inputSchema: {
|
|
207
255
|
type: "object",
|
|
208
|
-
properties: {
|
|
256
|
+
properties: {
|
|
257
|
+
search: {
|
|
258
|
+
type: "string",
|
|
259
|
+
description: "Optional: fuzzy search query to filter collections by name (e.g., 'hero' or 'compliance')",
|
|
260
|
+
},
|
|
261
|
+
},
|
|
209
262
|
},
|
|
210
263
|
},
|
|
211
264
|
{
|
|
@@ -276,7 +329,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
276
329
|
},
|
|
277
330
|
{
|
|
278
331
|
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.
|
|
332
|
+
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
333
|
inputSchema: {
|
|
281
334
|
type: "object",
|
|
282
335
|
properties: {
|
|
@@ -316,7 +369,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
316
369
|
},
|
|
317
370
|
{
|
|
318
371
|
name: "deploy_files",
|
|
319
|
-
description: "Deploy a multi-file Next.js project to VibeSharing.
|
|
372
|
+
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
373
|
inputSchema: {
|
|
321
374
|
type: "object",
|
|
322
375
|
properties: {
|
|
@@ -346,13 +399,17 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
346
399
|
type: "string",
|
|
347
400
|
description: "Optional: Git commit message (default: 'Deploy via MCP')",
|
|
348
401
|
},
|
|
402
|
+
deploy_name: {
|
|
403
|
+
type: "string",
|
|
404
|
+
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.",
|
|
405
|
+
},
|
|
349
406
|
},
|
|
350
407
|
required: ["prototype_id", "files"],
|
|
351
408
|
},
|
|
352
409
|
},
|
|
353
410
|
{
|
|
354
411
|
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
|
|
412
|
+
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
413
|
inputSchema: {
|
|
357
414
|
type: "object",
|
|
358
415
|
properties: {
|
|
@@ -380,10 +437,35 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
380
437
|
type: "string",
|
|
381
438
|
description: "Optional: Description of the prototype.",
|
|
382
439
|
},
|
|
440
|
+
deploy_name: {
|
|
441
|
+
type: "string",
|
|
442
|
+
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.",
|
|
443
|
+
},
|
|
383
444
|
},
|
|
384
445
|
required: ["repo_url"],
|
|
385
446
|
},
|
|
386
447
|
},
|
|
448
|
+
{
|
|
449
|
+
name: "resolve_target",
|
|
450
|
+
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.",
|
|
451
|
+
inputSchema: {
|
|
452
|
+
type: "object",
|
|
453
|
+
properties: {
|
|
454
|
+
collection_name: {
|
|
455
|
+
type: "string",
|
|
456
|
+
description: "Approximate collection name to search for (e.g., 'hero use cases'). Fuzzy matched.",
|
|
457
|
+
},
|
|
458
|
+
project_name: {
|
|
459
|
+
type: "string",
|
|
460
|
+
description: "Approximate project/prototype name to search for (e.g., 'ERG v3'). Fuzzy matched.",
|
|
461
|
+
},
|
|
462
|
+
deploy_name: {
|
|
463
|
+
type: "string",
|
|
464
|
+
description: "Desired Vercel deploy name to check availability for (e.g., 'erg-v3-teams').",
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
},
|
|
387
469
|
{
|
|
388
470
|
name: "add_context_link",
|
|
389
471
|
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.",
|
|
@@ -455,6 +537,40 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
455
537
|
switch (name) {
|
|
456
538
|
case "register_prototype": {
|
|
457
539
|
const params = args;
|
|
540
|
+
// Guardrail: if no collection specified, bounce back with options
|
|
541
|
+
if (!params.collection_id) {
|
|
542
|
+
const [guardCollResult, guardProtoResult] = await Promise.all([
|
|
543
|
+
client.listCollections(),
|
|
544
|
+
client.listPrototypes(),
|
|
545
|
+
]);
|
|
546
|
+
const guardCollections = guardCollResult.collections || [];
|
|
547
|
+
const guardPrototypes = guardProtoResult.prototypes || [];
|
|
548
|
+
const similarProjects = fuzzyMatch(params.name, guardPrototypes, (p) => p.name).slice(0, 5);
|
|
549
|
+
const sections = [
|
|
550
|
+
`HOLD ON — confirm with the user before registering "${params.name}":\n`,
|
|
551
|
+
];
|
|
552
|
+
if (similarProjects.length > 0) {
|
|
553
|
+
sections.push(`Similar existing projects:\n` +
|
|
554
|
+
similarProjects.map((m) => {
|
|
555
|
+
const p = m.item;
|
|
556
|
+
return ` - "${p.name}" (ID: ${p.id})`;
|
|
557
|
+
}).join("\n") +
|
|
558
|
+
`\n\nAsk the user: Is this an update to one of these, or a new project?`);
|
|
559
|
+
}
|
|
560
|
+
if (guardCollections.length > 0) {
|
|
561
|
+
sections.push(`\nNo collection specified. Available collections:\n` +
|
|
562
|
+
guardCollections.map((c) => ` - ${c.name} (ID: ${c.id})`).join("\n") +
|
|
563
|
+
`\n\nAsk the user: Which collection should "${params.name}" go in?`);
|
|
564
|
+
}
|
|
565
|
+
return {
|
|
566
|
+
content: [
|
|
567
|
+
{
|
|
568
|
+
type: "text",
|
|
569
|
+
text: sections.join("\n"),
|
|
570
|
+
},
|
|
571
|
+
],
|
|
572
|
+
};
|
|
573
|
+
}
|
|
458
574
|
const { source_code, source_filename, ...registerParams } = params;
|
|
459
575
|
const result = await client.registerPrototype(registerParams);
|
|
460
576
|
const protoId = result.prototype?.id;
|
|
@@ -479,14 +595,21 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
479
595
|
};
|
|
480
596
|
}
|
|
481
597
|
case "list_prototypes": {
|
|
598
|
+
const { search: protoSearch } = (args || {});
|
|
482
599
|
const result = await client.listPrototypes();
|
|
483
|
-
|
|
600
|
+
let prototypes = result.prototypes || [];
|
|
601
|
+
if (protoSearch) {
|
|
602
|
+
const matches = fuzzyMatch(protoSearch, prototypes, (p) => p.name);
|
|
603
|
+
prototypes = matches.map((m) => m.item);
|
|
604
|
+
}
|
|
484
605
|
if (prototypes.length === 0) {
|
|
485
606
|
return {
|
|
486
607
|
content: [
|
|
487
608
|
{
|
|
488
609
|
type: "text",
|
|
489
|
-
text:
|
|
610
|
+
text: protoSearch
|
|
611
|
+
? `No prototypes matching "${protoSearch}". Use list_prototypes without search to see all.`
|
|
612
|
+
: "No prototypes found. Use register_prototype to add your first one!",
|
|
490
613
|
},
|
|
491
614
|
],
|
|
492
615
|
};
|
|
@@ -498,20 +621,27 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
498
621
|
content: [
|
|
499
622
|
{
|
|
500
623
|
type: "text",
|
|
501
|
-
text: `Found ${prototypes.length} prototype(s):\n\n${list}`,
|
|
624
|
+
text: `Found ${prototypes.length} prototype(s)${protoSearch ? ` matching "${protoSearch}"` : ""}:\n\n${list}`,
|
|
502
625
|
},
|
|
503
626
|
],
|
|
504
627
|
};
|
|
505
628
|
}
|
|
506
629
|
case "list_collections": {
|
|
630
|
+
const { search: collSearch } = (args || {});
|
|
507
631
|
const result = await client.listCollections();
|
|
508
|
-
|
|
632
|
+
let collections = result.collections || [];
|
|
633
|
+
if (collSearch) {
|
|
634
|
+
const matches = fuzzyMatch(collSearch, collections, (c) => c.name);
|
|
635
|
+
collections = matches.map((m) => m.item);
|
|
636
|
+
}
|
|
509
637
|
if (collections.length === 0) {
|
|
510
638
|
return {
|
|
511
639
|
content: [
|
|
512
640
|
{
|
|
513
641
|
type: "text",
|
|
514
|
-
text:
|
|
642
|
+
text: collSearch
|
|
643
|
+
? `No collections matching "${collSearch}". Use list_collections without search to see all.`
|
|
644
|
+
: "No collections found. Create one in the VibeSharing dashboard first.",
|
|
515
645
|
},
|
|
516
646
|
],
|
|
517
647
|
};
|
|
@@ -523,7 +653,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
523
653
|
content: [
|
|
524
654
|
{
|
|
525
655
|
type: "text",
|
|
526
|
-
text: `Found ${collections.length} collection(s):\n\n${list}`,
|
|
656
|
+
text: `Found ${collections.length} collection(s)${collSearch ? ` matching "${collSearch}"` : ""}:\n\n${list}`,
|
|
527
657
|
},
|
|
528
658
|
],
|
|
529
659
|
};
|
|
@@ -610,6 +740,46 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
610
740
|
}
|
|
611
741
|
case "deploy_prototype": {
|
|
612
742
|
const { code, name, prototype_id } = args;
|
|
743
|
+
// Guardrail: if no existing prototype specified, bounce back with options
|
|
744
|
+
if (!prototype_id) {
|
|
745
|
+
const [guardCollResult, guardProtoResult] = await Promise.all([
|
|
746
|
+
client.listCollections(),
|
|
747
|
+
client.listPrototypes(),
|
|
748
|
+
]);
|
|
749
|
+
const guardCollections = guardCollResult.collections || [];
|
|
750
|
+
const guardPrototypes = guardProtoResult.prototypes || [];
|
|
751
|
+
const similarProjects = fuzzyMatch(name, guardPrototypes, (p) => p.name).slice(0, 5);
|
|
752
|
+
const sections = [
|
|
753
|
+
`HOLD ON — confirm with the user before deploying "${name}":\n`,
|
|
754
|
+
];
|
|
755
|
+
if (similarProjects.length > 0) {
|
|
756
|
+
sections.push(`Similar existing projects:\n` +
|
|
757
|
+
similarProjects.map((m) => {
|
|
758
|
+
const p = m.item;
|
|
759
|
+
return ` - "${p.name}" (ID: ${p.id})${p.external_url ? ` → ${p.external_url}` : ""}`;
|
|
760
|
+
}).join("\n") +
|
|
761
|
+
`\n\nAsk the user: Is this an UPDATE to one of these, or a NEW project?`);
|
|
762
|
+
}
|
|
763
|
+
if (guardCollections.length > 0) {
|
|
764
|
+
sections.push(`\nAvailable collections:\n` +
|
|
765
|
+
guardCollections.map((c) => ` - ${c.name} (ID: ${c.id})`).join("\n") +
|
|
766
|
+
`\n\nAsk the user: Which collection should this go in?`);
|
|
767
|
+
}
|
|
768
|
+
const suggestedSlug = name
|
|
769
|
+
.toLowerCase()
|
|
770
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
771
|
+
.replace(/^-+|-+$/g, "");
|
|
772
|
+
sections.push(`\nSuggested deploy name: "${suggestedSlug}" → https://${suggestedSlug}.vercel.app\n` +
|
|
773
|
+
`Ask the user: Do you want to use this name or choose a different one?`);
|
|
774
|
+
return {
|
|
775
|
+
content: [
|
|
776
|
+
{
|
|
777
|
+
type: "text",
|
|
778
|
+
text: sections.join("\n"),
|
|
779
|
+
},
|
|
780
|
+
],
|
|
781
|
+
};
|
|
782
|
+
}
|
|
613
783
|
// If no prototype_id, first register a new prototype
|
|
614
784
|
let prototypeId = prototype_id;
|
|
615
785
|
if (!prototypeId) {
|
|
@@ -658,28 +828,77 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
658
828
|
};
|
|
659
829
|
}
|
|
660
830
|
case "deploy_files": {
|
|
661
|
-
const { prototype_id: deployProtoId, files, commit_message } = args;
|
|
662
|
-
const deployResult = await client.deployFiles(deployProtoId, files, commit_message || "Deploy via MCP");
|
|
831
|
+
const { prototype_id: deployProtoId, files, commit_message, deploy_name: deployName } = args;
|
|
832
|
+
const deployResult = await client.deployFiles(deployProtoId, files, commit_message || "Deploy via MCP", deployName);
|
|
663
833
|
return {
|
|
664
834
|
content: [
|
|
665
835
|
{
|
|
666
836
|
type: "text",
|
|
667
|
-
text: `Deployed ${files.length} files!\n\
|
|
837
|
+
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.`,
|
|
668
838
|
},
|
|
669
839
|
],
|
|
670
840
|
};
|
|
671
841
|
}
|
|
672
842
|
case "import_repo": {
|
|
673
|
-
const { repo_url, name: importName, prototype_id: importProtoId, collection_id: importCollectionId, parent_project_id: importParentId, description: importDesc, } = args;
|
|
674
|
-
//
|
|
843
|
+
const { repo_url, name: importName, prototype_id: importProtoId, collection_id: importCollectionId, parent_project_id: importParentId, description: importDesc, deploy_name: deployName, } = args;
|
|
844
|
+
// Guardrail: if no collection and no existing prototype specified, bounce back with options
|
|
845
|
+
if (!importCollectionId && !importProtoId) {
|
|
846
|
+
const [guardCollResult, guardProtoResult] = await Promise.all([
|
|
847
|
+
client.listCollections(),
|
|
848
|
+
client.listPrototypes(),
|
|
849
|
+
]);
|
|
850
|
+
const guardCollections = guardCollResult.collections || [];
|
|
851
|
+
const guardPrototypes = guardProtoResult.prototypes || [];
|
|
852
|
+
const repoName = repo_url.replace(/\.git$/, "").split("/").pop() || "imported-prototype";
|
|
853
|
+
const suggestedName = importName || repoName.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
854
|
+
// Check for similar existing prototypes
|
|
855
|
+
const similarProjects = importName
|
|
856
|
+
? fuzzyMatch(importName, guardPrototypes, (p) => p.name).slice(0, 5)
|
|
857
|
+
: [];
|
|
858
|
+
const sections = [
|
|
859
|
+
`HOLD ON — confirm these details with the user before deploying:\n`,
|
|
860
|
+
`Prototype name: "${suggestedName}"`,
|
|
861
|
+
];
|
|
862
|
+
if (similarProjects.length > 0) {
|
|
863
|
+
sections.push(`\nSimilar existing projects found:\n` +
|
|
864
|
+
similarProjects.map((m) => {
|
|
865
|
+
const p = m.item;
|
|
866
|
+
return ` - "${p.name}" (ID: ${p.id})${p.external_url ? ` → ${p.external_url}` : ""}`;
|
|
867
|
+
}).join("\n") +
|
|
868
|
+
`\n\nAsk the user: Is this an UPDATE to one of these existing projects, or a NEW project?`);
|
|
869
|
+
}
|
|
870
|
+
if (guardCollections.length > 0) {
|
|
871
|
+
sections.push(`\nNo collection specified. Available collections:\n` +
|
|
872
|
+
guardCollections.map((c) => ` - ${c.name} (ID: ${c.id})`).join("\n") +
|
|
873
|
+
`\n\nAsk the user: Which collection should "${suggestedName}" go in?`);
|
|
874
|
+
}
|
|
875
|
+
if (!deployName) {
|
|
876
|
+
const suggestedSlug = suggestedName
|
|
877
|
+
.toLowerCase()
|
|
878
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
879
|
+
.replace(/^-+|-+$/g, "");
|
|
880
|
+
sections.push(`\nNo deploy name specified. Suggested: "${suggestedSlug}" → https://${suggestedSlug}.vercel.app\n` +
|
|
881
|
+
`Ask the user: Do you want to use "${suggestedSlug}" or choose a different deploy name?`);
|
|
882
|
+
}
|
|
883
|
+
return {
|
|
884
|
+
content: [
|
|
885
|
+
{
|
|
886
|
+
type: "text",
|
|
887
|
+
text: sections.join("\n"),
|
|
888
|
+
},
|
|
889
|
+
],
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
// Auto-create prototype if no ID provided (collection was confirmed above)
|
|
675
893
|
let protoId = importProtoId;
|
|
676
894
|
if (!protoId) {
|
|
677
895
|
// Derive name from repo URL if not provided
|
|
678
896
|
const repoName = repo_url.replace(/\.git$/, "").split("/").pop() || "imported-prototype";
|
|
679
|
-
const protoName = importName || repoName.replace(/[-_]+/g, " ").replace(/\b\w/g, c => c.toUpperCase());
|
|
897
|
+
const protoName = importName || repoName.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
680
898
|
const registered = await client.registerPrototype({
|
|
681
899
|
name: protoName,
|
|
682
900
|
description: importDesc || `Imported from ${repo_url}`,
|
|
901
|
+
external_url: repo_url,
|
|
683
902
|
collection_id: importCollectionId,
|
|
684
903
|
parent_project_id: importParentId,
|
|
685
904
|
});
|
|
@@ -696,12 +915,120 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
696
915
|
isError: true,
|
|
697
916
|
};
|
|
698
917
|
}
|
|
699
|
-
const importResult = await client.importRepo(protoId, repo_url);
|
|
918
|
+
const importResult = await client.importRepo(protoId, repo_url, deployName);
|
|
919
|
+
return {
|
|
920
|
+
content: [
|
|
921
|
+
{
|
|
922
|
+
type: "text",
|
|
923
|
+
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.`,
|
|
924
|
+
},
|
|
925
|
+
],
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
case "resolve_target": {
|
|
929
|
+
const { collection_name: collName, project_name: projName, deploy_name: desiredDeployName, } = (args || {});
|
|
930
|
+
// Fetch collections and prototypes in parallel
|
|
931
|
+
const [collResult, protoResult] = await Promise.all([
|
|
932
|
+
client.listCollections(),
|
|
933
|
+
client.listPrototypes(),
|
|
934
|
+
]);
|
|
935
|
+
const allCollections = collResult.collections || [];
|
|
936
|
+
const allPrototypes = protoResult.prototypes || [];
|
|
937
|
+
const sections = [];
|
|
938
|
+
// --- Collection matching ---
|
|
939
|
+
if (collName) {
|
|
940
|
+
const collMatches = fuzzyMatch(collName, allCollections, (c) => c.name);
|
|
941
|
+
if (collMatches.length === 0) {
|
|
942
|
+
sections.push(`COLLECTION: No match for "${collName}".\n` +
|
|
943
|
+
`Available collections:\n` +
|
|
944
|
+
allCollections
|
|
945
|
+
.map((c) => ` - ${c.name} (ID: ${c.id})`)
|
|
946
|
+
.join("\n") +
|
|
947
|
+
`\n\nAsk the user: Which collection should this go in? Or create a new one?`);
|
|
948
|
+
}
|
|
949
|
+
else if (collMatches[0].score >= 0.9) {
|
|
950
|
+
const best = collMatches[0].item;
|
|
951
|
+
sections.push(`COLLECTION: Matched "${collName}" → "${best.name}" (ID: ${best.id})`);
|
|
952
|
+
// Show prototypes in this collection for context
|
|
953
|
+
const collProtos = allPrototypes.filter((p) => p.folder_id === best.id);
|
|
954
|
+
if (collProtos.length > 0) {
|
|
955
|
+
sections.push(`Existing projects in "${best.name}":\n` +
|
|
956
|
+
collProtos
|
|
957
|
+
.map((p) => ` - ${p.name} (ID: ${p.id})${p.external_url ? ` → ${p.external_url}` : ""}`)
|
|
958
|
+
.join("\n") +
|
|
959
|
+
`\n\nAsk the user: Should this be a NEW project in "${best.name}", or an update to one of these existing projects?`);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
// Fuzzy matches but not confident
|
|
964
|
+
sections.push(`COLLECTION: "${collName}" is not an exact match. Did the user mean one of these?\n` +
|
|
965
|
+
collMatches
|
|
966
|
+
.slice(0, 5)
|
|
967
|
+
.map((m) => {
|
|
968
|
+
const c = m.item;
|
|
969
|
+
return ` - "${c.name}" (ID: ${c.id}, confidence: ${Math.round(m.score * 100)}%)`;
|
|
970
|
+
})
|
|
971
|
+
.join("\n") +
|
|
972
|
+
`\n\nAsk the user to confirm which collection.`);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
// No collection name given — show all options
|
|
977
|
+
if (allCollections.length > 0) {
|
|
978
|
+
sections.push(`COLLECTION: Not specified. Available collections:\n` +
|
|
979
|
+
allCollections
|
|
980
|
+
.map((c) => ` - ${c.name} (ID: ${c.id})`)
|
|
981
|
+
.join("\n") +
|
|
982
|
+
`\n\nAsk the user: Which collection should this prototype go in? Or create a new one?`);
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
sections.push(`COLLECTION: No collections exist yet. Ask the user if they want to create one.`);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
// --- Project matching ---
|
|
989
|
+
if (projName) {
|
|
990
|
+
const projMatches = fuzzyMatch(projName, allPrototypes, (p) => p.name);
|
|
991
|
+
if (projMatches.length === 0) {
|
|
992
|
+
sections.push(`PROJECT: No match for "${projName}". This will be a new project.\n` +
|
|
993
|
+
`Ask the user to confirm the name for the new project.`);
|
|
994
|
+
}
|
|
995
|
+
else if (projMatches[0].score >= 0.9) {
|
|
996
|
+
const best = projMatches[0].item;
|
|
997
|
+
sections.push(`PROJECT: Matched "${projName}" → "${best.name}" (ID: ${best.id})${best.external_url ? `\n Current URL: ${best.external_url}` : ""}\n\n` +
|
|
998
|
+
`Ask the user: Deploy as an UPDATE to "${best.name}", or create a NEW project?`);
|
|
999
|
+
}
|
|
1000
|
+
else {
|
|
1001
|
+
sections.push(`PROJECT: "${projName}" is not an exact match. Close matches:\n` +
|
|
1002
|
+
projMatches
|
|
1003
|
+
.slice(0, 5)
|
|
1004
|
+
.map((m) => {
|
|
1005
|
+
const p = m.item;
|
|
1006
|
+
return ` - "${p.name}" (ID: ${p.id}, confidence: ${Math.round(m.score * 100)}%)`;
|
|
1007
|
+
})
|
|
1008
|
+
.join("\n") +
|
|
1009
|
+
`\n\nAsk the user: Is this an update to one of these, or a new project?`);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
// --- Deploy name ---
|
|
1013
|
+
if (desiredDeployName) {
|
|
1014
|
+
const slug = desiredDeployName
|
|
1015
|
+
.toLowerCase()
|
|
1016
|
+
.replace(/[^a-z0-9]/g, "-")
|
|
1017
|
+
.replace(/-{2,}/g, "-")
|
|
1018
|
+
.replace(/^-+|-+$/g, "");
|
|
1019
|
+
sections.push(`DEPLOY NAME: "${slug}" → will deploy to https://${slug}.vercel.app\n` +
|
|
1020
|
+
`(Availability will be checked at deploy time. If taken, a suffix will be added.)\n\n` +
|
|
1021
|
+
`Ask the user: Do you want to name this deployment "${slug}"?`);
|
|
1022
|
+
}
|
|
1023
|
+
else {
|
|
1024
|
+
sections.push(`DEPLOY NAME: Not specified.\n` +
|
|
1025
|
+
`Ask the user: Do you want a custom deploy name (e.g., "erg-v3-teams" → erg-v3-teams.vercel.app), or auto-generate one?`);
|
|
1026
|
+
}
|
|
700
1027
|
return {
|
|
701
1028
|
content: [
|
|
702
1029
|
{
|
|
703
1030
|
type: "text",
|
|
704
|
-
text:
|
|
1031
|
+
text: sections.join("\n\n---\n\n"),
|
|
705
1032
|
},
|
|
706
1033
|
],
|
|
707
1034
|
};
|
package/package.json
CHANGED