@yassinello/create-mymcp 0.1.1 → 0.3.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/README.md +1 -1
- package/index.mjs +274 -53
- package/package.json +29 -29
package/README.md
CHANGED
package/index.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// create-mymcp — Interactive installer for MyMCP
|
|
4
|
-
// Usage: npx create-mymcp@latest
|
|
4
|
+
// Usage: npx @yassinello/create-mymcp@latest
|
|
5
5
|
|
|
6
6
|
import { execSync, spawnSync } from "node:child_process";
|
|
7
7
|
import { createInterface } from "node:readline";
|
|
8
8
|
import { randomBytes } from "node:crypto";
|
|
9
|
-
import { existsSync, writeFileSync, readFileSync } from "node:fs";
|
|
10
|
-
import { join, resolve } from "node:path";
|
|
9
|
+
import { existsSync, writeFileSync, readFileSync, readdirSync } from "node:fs";
|
|
10
|
+
import { join, resolve, isAbsolute } from "node:path";
|
|
11
11
|
|
|
12
12
|
// ── Helpers ──────────────────────────────────────────────────────────
|
|
13
13
|
|
|
@@ -48,6 +48,151 @@ async function confirm(msg, defaultYes = true) {
|
|
|
48
48
|
return answer === "y" || answer === "yes";
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/** Strip surrounding quotes and trim whitespace */
|
|
52
|
+
function cleanPath(input) {
|
|
53
|
+
return input.trim().replace(/^["']|["']$/g, "");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Check if a directory exists and is non-empty */
|
|
57
|
+
function isDirNonEmpty(dir) {
|
|
58
|
+
try {
|
|
59
|
+
return readdirSync(dir).length > 0;
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Clean a credential value:
|
|
67
|
+
* - Strip "KEY=" prefix if user pasted "GOOGLE_CLIENT_ID=value"
|
|
68
|
+
* - Return null for non-values like "NA", "N/A", "none", "skip", "-", ""
|
|
69
|
+
*/
|
|
70
|
+
function cleanCredential(value, expectedKey) {
|
|
71
|
+
let cleaned = value.trim();
|
|
72
|
+
|
|
73
|
+
// Strip "KEY=" prefix if user pasted the whole line
|
|
74
|
+
const prefixPattern = new RegExp(`^${expectedKey}\\s*=\\s*`, "i");
|
|
75
|
+
cleaned = cleaned.replace(prefixPattern, "");
|
|
76
|
+
|
|
77
|
+
// Also strip any generic KEY= prefix (e.g., user pasted from .env)
|
|
78
|
+
cleaned = cleaned.replace(/^[A-Z_]+=/, "");
|
|
79
|
+
|
|
80
|
+
// Detect non-values
|
|
81
|
+
const skipValues = ["na", "n/a", "none", "skip", "-", "no", ""];
|
|
82
|
+
if (skipValues.includes(cleaned.toLowerCase())) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return cleaned;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Normalize a timezone input:
|
|
91
|
+
* - "Paris" → "Europe/Paris"
|
|
92
|
+
* - "New York" → "America/New_York"
|
|
93
|
+
* - "UTC" → "UTC"
|
|
94
|
+
*/
|
|
95
|
+
const TIMEZONE_SHORTCUTS = {
|
|
96
|
+
paris: "Europe/Paris",
|
|
97
|
+
london: "Europe/London",
|
|
98
|
+
berlin: "Europe/Berlin",
|
|
99
|
+
amsterdam: "Europe/Amsterdam",
|
|
100
|
+
brussels: "Europe/Brussels",
|
|
101
|
+
madrid: "Europe/Madrid",
|
|
102
|
+
rome: "Europe/Rome",
|
|
103
|
+
lisbon: "Europe/Lisbon",
|
|
104
|
+
zurich: "Europe/Zurich",
|
|
105
|
+
tokyo: "Asia/Tokyo",
|
|
106
|
+
shanghai: "Asia/Shanghai",
|
|
107
|
+
singapore: "Asia/Singapore",
|
|
108
|
+
dubai: "Asia/Dubai",
|
|
109
|
+
mumbai: "Asia/Kolkata",
|
|
110
|
+
sydney: "Australia/Sydney",
|
|
111
|
+
auckland: "Pacific/Auckland",
|
|
112
|
+
"new york": "America/New_York",
|
|
113
|
+
"new_york": "America/New_York",
|
|
114
|
+
"los angeles": "America/Los_Angeles",
|
|
115
|
+
"los_angeles": "America/Los_Angeles",
|
|
116
|
+
chicago: "America/Chicago",
|
|
117
|
+
denver: "America/Denver",
|
|
118
|
+
toronto: "America/Toronto",
|
|
119
|
+
"sao paulo": "America/Sao_Paulo",
|
|
120
|
+
"são paulo": "America/Sao_Paulo",
|
|
121
|
+
montreal: "America/Montreal",
|
|
122
|
+
utc: "UTC",
|
|
123
|
+
gmt: "UTC",
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
function normalizeTimezone(input) {
|
|
127
|
+
const lower = input.trim().toLowerCase();
|
|
128
|
+
if (TIMEZONE_SHORTCUTS[lower]) return TIMEZONE_SHORTCUTS[lower];
|
|
129
|
+
// If it already looks like a valid IANA timezone (contains /), return as-is
|
|
130
|
+
if (input.includes("/")) return input.trim();
|
|
131
|
+
// Default
|
|
132
|
+
return input.trim();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Normalize a locale input:
|
|
137
|
+
* - "fr" → "fr-FR"
|
|
138
|
+
* - "en" → "en-US"
|
|
139
|
+
* - "de" → "de-DE"
|
|
140
|
+
*/
|
|
141
|
+
const LOCALE_SHORTCUTS = {
|
|
142
|
+
fr: "fr-FR",
|
|
143
|
+
en: "en-US",
|
|
144
|
+
de: "de-DE",
|
|
145
|
+
es: "es-ES",
|
|
146
|
+
it: "it-IT",
|
|
147
|
+
pt: "pt-PT",
|
|
148
|
+
nl: "nl-NL",
|
|
149
|
+
ja: "ja-JP",
|
|
150
|
+
zh: "zh-CN",
|
|
151
|
+
ko: "ko-KR",
|
|
152
|
+
ar: "ar-SA",
|
|
153
|
+
ru: "ru-RU",
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
function normalizeLocale(input) {
|
|
157
|
+
const trimmed = input.trim();
|
|
158
|
+
const lower = trimmed.toLowerCase();
|
|
159
|
+
if (LOCALE_SHORTCUTS[lower]) return LOCALE_SHORTCUTS[lower];
|
|
160
|
+
return trimmed;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Extract owner/repo from a GitHub URL or return as-is
|
|
165
|
+
* - "https://github.com/Yassinello/obsidyass" → "Yassinello/obsidyass"
|
|
166
|
+
* - "Yassinello/obsidyass" → "Yassinello/obsidyass"
|
|
167
|
+
*/
|
|
168
|
+
function normalizeGitHubRepo(input) {
|
|
169
|
+
const cleaned = input.trim().replace(/\/+$/, ""); // strip trailing slashes
|
|
170
|
+
const urlMatch = cleaned.match(/github\.com\/([^/]+\/[^/]+)/);
|
|
171
|
+
if (urlMatch) return urlMatch[1];
|
|
172
|
+
return cleaned;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Slugify a project name for Vercel:
|
|
177
|
+
* - Lowercase, replace spaces with dashes, strip invalid chars
|
|
178
|
+
*/
|
|
179
|
+
function slugify(name) {
|
|
180
|
+
return name
|
|
181
|
+
.toLowerCase()
|
|
182
|
+
.replace(/\s+/g, "-")
|
|
183
|
+
.replace(/[^a-z0-9._-]/g, "")
|
|
184
|
+
.replace(/---+/g, "--") // Vercel doesn't allow ---
|
|
185
|
+
.slice(0, 100);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Mask a secret for display: show first 4 chars, then ***
|
|
190
|
+
*/
|
|
191
|
+
function maskSecret(value) {
|
|
192
|
+
if (value.length <= 8) return "****";
|
|
193
|
+
return value.slice(0, 4) + "****" + value.slice(-4);
|
|
194
|
+
}
|
|
195
|
+
|
|
51
196
|
// ── Pack definitions ─────────────────────────────────────────────────
|
|
52
197
|
|
|
53
198
|
const PACKS = [
|
|
@@ -61,12 +206,13 @@ const PACKS = [
|
|
|
61
206
|
prompt: "Google OAuth Client ID",
|
|
62
207
|
help: "https://console.cloud.google.com/apis/credentials",
|
|
63
208
|
},
|
|
64
|
-
{ key: "GOOGLE_CLIENT_SECRET", prompt: "Google OAuth Client Secret" },
|
|
209
|
+
{ key: "GOOGLE_CLIENT_SECRET", prompt: "Google OAuth Client Secret", sensitive: true },
|
|
65
210
|
{
|
|
66
211
|
key: "GOOGLE_REFRESH_TOKEN",
|
|
67
212
|
prompt: "Google OAuth Refresh Token",
|
|
68
213
|
help: "Run the OAuth flow after deploy at /api/auth/google",
|
|
69
214
|
optional: true,
|
|
215
|
+
sensitive: true,
|
|
70
216
|
},
|
|
71
217
|
],
|
|
72
218
|
},
|
|
@@ -79,10 +225,13 @@ const PACKS = [
|
|
|
79
225
|
key: "GITHUB_PAT",
|
|
80
226
|
prompt: "GitHub PAT (with repo scope)",
|
|
81
227
|
help: "https://github.com/settings/tokens",
|
|
228
|
+
sensitive: true,
|
|
82
229
|
},
|
|
83
230
|
{
|
|
84
231
|
key: "GITHUB_REPO",
|
|
85
|
-
prompt: "GitHub repo
|
|
232
|
+
prompt: "GitHub repo",
|
|
233
|
+
example: "owner/repo or https://github.com/owner/repo",
|
|
234
|
+
transform: normalizeGitHubRepo,
|
|
86
235
|
},
|
|
87
236
|
],
|
|
88
237
|
},
|
|
@@ -95,36 +244,53 @@ const PACKS = [
|
|
|
95
244
|
key: "BROWSERBASE_API_KEY",
|
|
96
245
|
prompt: "Browserbase API key",
|
|
97
246
|
help: "https://browserbase.com",
|
|
247
|
+
sensitive: true,
|
|
98
248
|
},
|
|
99
249
|
{ key: "BROWSERBASE_PROJECT_ID", prompt: "Browserbase Project ID" },
|
|
100
250
|
{
|
|
101
251
|
key: "OPENROUTER_API_KEY",
|
|
102
252
|
prompt: "OpenRouter API key",
|
|
103
253
|
help: "https://openrouter.ai/keys",
|
|
254
|
+
sensitive: true,
|
|
104
255
|
},
|
|
105
256
|
],
|
|
106
257
|
},
|
|
107
258
|
{
|
|
108
259
|
id: "slack",
|
|
109
260
|
name: "Slack",
|
|
110
|
-
tools: "Channels,
|
|
261
|
+
tools: "Channels, messages, threads, profiles, search (6 tools)",
|
|
111
262
|
vars: [
|
|
112
263
|
{
|
|
113
264
|
key: "SLACK_BOT_TOKEN",
|
|
114
265
|
prompt: "Slack Bot User OAuth Token",
|
|
115
266
|
help: "https://api.slack.com/apps → OAuth & Permissions",
|
|
267
|
+
sensitive: true,
|
|
116
268
|
},
|
|
117
269
|
],
|
|
118
270
|
},
|
|
119
271
|
{
|
|
120
272
|
id: "notion",
|
|
121
273
|
name: "Notion",
|
|
122
|
-
tools: "Search, read, create (
|
|
274
|
+
tools: "Search, read, create, update, query databases (5 tools)",
|
|
123
275
|
vars: [
|
|
124
276
|
{
|
|
125
277
|
key: "NOTION_API_KEY",
|
|
126
278
|
prompt: "Notion Internal Integration Token",
|
|
127
279
|
help: "https://www.notion.so/my-integrations",
|
|
280
|
+
sensitive: true,
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: "composio",
|
|
286
|
+
name: "Composio",
|
|
287
|
+
tools: "1000+ app integrations — Jira, HubSpot, Salesforce, Airtable... (2 tools)",
|
|
288
|
+
vars: [
|
|
289
|
+
{
|
|
290
|
+
key: "COMPOSIO_API_KEY",
|
|
291
|
+
prompt: "Composio API key",
|
|
292
|
+
help: "https://composio.dev → Settings",
|
|
293
|
+
sensitive: true,
|
|
128
294
|
},
|
|
129
295
|
],
|
|
130
296
|
},
|
|
@@ -152,21 +318,21 @@ async function main() {
|
|
|
152
318
|
step("1/5", "Project setup");
|
|
153
319
|
|
|
154
320
|
const defaultDir = "mymcp";
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
321
|
+
info(`Just type a folder name, or a full path. Default: ${defaultDir}`);
|
|
322
|
+
const rawInput = (await ask(` Project directory [${defaultDir}]: `)).trim();
|
|
323
|
+
const cleaned = cleanPath(rawInput) || defaultDir;
|
|
324
|
+
|
|
325
|
+
const projectDir = isAbsolute(cleaned) ? cleaned : resolve(cleaned);
|
|
159
326
|
const projectName = projectDir.split(/[/\\]/).pop();
|
|
160
327
|
|
|
161
|
-
if (existsSync(projectDir)) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
rl.close();
|
|
166
|
-
process.exit(1);
|
|
167
|
-
}
|
|
328
|
+
if (existsSync(projectDir) && isDirNonEmpty(projectDir)) {
|
|
329
|
+
log(` ${RED}✗${RESET} Directory "${projectDir}" already exists and is not empty.`);
|
|
330
|
+
rl.close();
|
|
331
|
+
process.exit(1);
|
|
168
332
|
}
|
|
169
333
|
|
|
334
|
+
info(`Will create: ${projectDir}`);
|
|
335
|
+
|
|
170
336
|
// ── Step 2: Clone ────────────────────────────────────────────────
|
|
171
337
|
|
|
172
338
|
step("2/5", "Cloning MyMCP");
|
|
@@ -189,21 +355,22 @@ async function main() {
|
|
|
189
355
|
process.exit(1);
|
|
190
356
|
}
|
|
191
357
|
|
|
192
|
-
// Set up upstream remote for easy updates
|
|
193
358
|
run(`git -C "${projectDir}" remote rename origin upstream`);
|
|
194
359
|
ok("Cloned and upstream remote configured");
|
|
195
|
-
info("Run `
|
|
360
|
+
info("Run `npm run update` to pull updates anytime");
|
|
196
361
|
|
|
197
362
|
// ── Step 3: Pick packs ───────────────────────────────────────────
|
|
198
363
|
|
|
199
364
|
step("3/5", "Choose your tool packs");
|
|
365
|
+
info("Press Enter to accept the default (shown in uppercase).");
|
|
200
366
|
log("");
|
|
201
367
|
|
|
202
368
|
const selectedPacks = [];
|
|
203
369
|
for (const pack of PACKS) {
|
|
370
|
+
const defaultOn = pack.id === "vault" || pack.id === "google";
|
|
204
371
|
const yes = await confirm(
|
|
205
372
|
`${BOLD}${pack.name}${RESET} — ${pack.tools}?`,
|
|
206
|
-
|
|
373
|
+
defaultOn
|
|
207
374
|
);
|
|
208
375
|
if (yes) selectedPacks.push(pack);
|
|
209
376
|
}
|
|
@@ -217,39 +384,69 @@ async function main() {
|
|
|
217
384
|
// ── Step 4: Collect credentials ──────────────────────────────────
|
|
218
385
|
|
|
219
386
|
step("4/5", "Configure credentials");
|
|
387
|
+
info("Paste just the value — not the KEY=value format.");
|
|
388
|
+
info("Type 'skip' or press Enter on optional fields to skip.");
|
|
220
389
|
|
|
221
390
|
const envVars = {};
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
ok(`MCP_AUTH_TOKEN generated: ${envVars.MCP_AUTH_TOKEN.slice(0, 8)}...`);
|
|
391
|
+
const mcpToken = randomBytes(32).toString("hex");
|
|
392
|
+
envVars.MCP_AUTH_TOKEN = mcpToken;
|
|
393
|
+
ok(`MCP_AUTH_TOKEN generated: ${maskSecret(mcpToken)}`);
|
|
226
394
|
|
|
227
395
|
// Instance settings
|
|
228
396
|
log("");
|
|
229
|
-
const
|
|
230
|
-
const
|
|
397
|
+
const tzRaw = (await ask(` Timezone (e.g. Europe/Paris, Tokyo, UTC) [UTC]: `)).trim() || "UTC";
|
|
398
|
+
const tz = normalizeTimezone(tzRaw);
|
|
399
|
+
if (tz !== tzRaw && tzRaw.toLowerCase() !== "utc") {
|
|
400
|
+
info(`Normalized to: ${tz}`);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const localeRaw = (await ask(` Locale (e.g. fr, en-US, de) [en-US]: `)).trim() || "en-US";
|
|
404
|
+
const locale = normalizeLocale(localeRaw);
|
|
405
|
+
if (locale !== localeRaw) {
|
|
406
|
+
info(`Normalized to: ${locale}`);
|
|
407
|
+
}
|
|
408
|
+
|
|
231
409
|
const displayName = (await ask(` Display name [User]: `)).trim() || "User";
|
|
410
|
+
|
|
232
411
|
envVars.MYMCP_TIMEZONE = tz;
|
|
233
412
|
envVars.MYMCP_LOCALE = locale;
|
|
234
413
|
envVars.MYMCP_DISPLAY_NAME = displayName;
|
|
235
414
|
|
|
236
415
|
// Pack credentials
|
|
416
|
+
const packStatus = []; // Track for recap
|
|
417
|
+
|
|
237
418
|
for (const pack of selectedPacks) {
|
|
238
419
|
log("");
|
|
239
420
|
log(` ${BOLD}${pack.name}${RESET}`);
|
|
421
|
+
let allSet = true;
|
|
422
|
+
|
|
240
423
|
for (const v of pack.vars) {
|
|
241
424
|
if (v.help) info(v.help);
|
|
242
|
-
if (v.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
425
|
+
if (v.example) info(`Format: ${v.example}`);
|
|
426
|
+
if (v.optional) info("(optional — press Enter to skip)");
|
|
427
|
+
|
|
428
|
+
const rawValue = (await ask(` ${v.prompt}: `)).trim();
|
|
429
|
+
const cleaned = cleanCredential(rawValue, v.key);
|
|
430
|
+
|
|
431
|
+
if (cleaned) {
|
|
432
|
+
// Apply transform if defined (e.g., GitHub URL → owner/repo)
|
|
433
|
+
const finalValue = v.transform ? v.transform(cleaned) : cleaned;
|
|
434
|
+
envVars[v.key] = finalValue;
|
|
435
|
+
|
|
436
|
+
if (v.sensitive) {
|
|
437
|
+
ok(`${v.key} set (${maskSecret(finalValue)})`);
|
|
438
|
+
} else {
|
|
439
|
+
ok(`${v.key} = ${finalValue}`);
|
|
440
|
+
}
|
|
441
|
+
} else if (v.optional) {
|
|
442
|
+
info(`${v.key} skipped`);
|
|
443
|
+
} else {
|
|
250
444
|
warn(`${v.key} skipped — ${pack.name} pack won't activate until set`);
|
|
445
|
+
allSet = false;
|
|
251
446
|
}
|
|
252
447
|
}
|
|
448
|
+
|
|
449
|
+
packStatus.push({ name: pack.name, active: allSet });
|
|
253
450
|
}
|
|
254
451
|
|
|
255
452
|
// ── Write .env ───────────────────────────────────────────────────
|
|
@@ -257,29 +454,27 @@ async function main() {
|
|
|
257
454
|
const envPath = join(projectDir, ".env");
|
|
258
455
|
const envExamplePath = join(projectDir, ".env.example");
|
|
259
456
|
|
|
260
|
-
// Read .env.example as base, then overlay collected values
|
|
261
457
|
let envContent = "# MyMCP — Generated by create-mymcp\n";
|
|
262
458
|
envContent += `# Created: ${new Date().toISOString().split("T")[0]}\n\n`;
|
|
263
459
|
|
|
460
|
+
const writtenVars = new Set();
|
|
461
|
+
|
|
264
462
|
if (existsSync(envExamplePath)) {
|
|
265
463
|
const example = readFileSync(envExamplePath, "utf-8");
|
|
266
|
-
// Parse .env.example and fill in values we collected
|
|
267
464
|
const lines = example.split("\n");
|
|
268
465
|
for (const line of lines) {
|
|
269
|
-
const match = line.match(/^([A-Z_]
|
|
466
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
270
467
|
if (match && envVars[match[1]] !== undefined) {
|
|
271
468
|
envContent += `${match[1]}=${envVars[match[1]]}\n`;
|
|
272
|
-
|
|
469
|
+
writtenVars.add(match[1]);
|
|
273
470
|
} else {
|
|
274
471
|
envContent += line + "\n";
|
|
275
472
|
}
|
|
276
473
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
} else {
|
|
282
|
-
for (const [key, value] of Object.entries(envVars)) {
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
477
|
+
if (!writtenVars.has(key)) {
|
|
283
478
|
envContent += `${key}=${value}\n`;
|
|
284
479
|
}
|
|
285
480
|
}
|
|
@@ -291,7 +486,6 @@ async function main() {
|
|
|
291
486
|
|
|
292
487
|
step("5/5", "Install & deploy");
|
|
293
488
|
|
|
294
|
-
// Install dependencies
|
|
295
489
|
log("");
|
|
296
490
|
info("Installing dependencies...");
|
|
297
491
|
const installResult = spawnSync("npm", ["install"], {
|
|
@@ -313,6 +507,8 @@ async function main() {
|
|
|
313
507
|
false
|
|
314
508
|
);
|
|
315
509
|
|
|
510
|
+
let deploySucceeded = false;
|
|
511
|
+
|
|
316
512
|
if (deployVercel) {
|
|
317
513
|
if (!hasCommand("vercel")) {
|
|
318
514
|
info("Installing Vercel CLI...");
|
|
@@ -322,16 +518,24 @@ async function main() {
|
|
|
322
518
|
});
|
|
323
519
|
}
|
|
324
520
|
|
|
521
|
+
// Slugify project name for Vercel
|
|
522
|
+
const vercelName = slugify(projectName);
|
|
523
|
+
if (vercelName !== projectName) {
|
|
524
|
+
info(`Vercel project name: ${vercelName} (slugified from "${projectName}")`);
|
|
525
|
+
}
|
|
526
|
+
|
|
325
527
|
log("");
|
|
326
528
|
info("Running vercel deploy...");
|
|
327
|
-
const vercelResult = spawnSync("vercel", ["--yes"], {
|
|
529
|
+
const vercelResult = spawnSync("vercel", ["--yes", "--name", vercelName], {
|
|
328
530
|
cwd: projectDir,
|
|
329
531
|
stdio: "inherit",
|
|
330
532
|
shell: true,
|
|
533
|
+
env: { ...process.env, NO_UPDATE_NOTIFIER: "1" },
|
|
331
534
|
});
|
|
332
535
|
|
|
333
536
|
if (vercelResult.status === 0) {
|
|
334
537
|
ok("Deployed to Vercel!");
|
|
538
|
+
deploySucceeded = true;
|
|
335
539
|
log("");
|
|
336
540
|
warn("Don't forget to add your env vars in the Vercel dashboard:");
|
|
337
541
|
info("Vercel → Project Settings → Environment Variables");
|
|
@@ -347,22 +551,39 @@ async function main() {
|
|
|
347
551
|
log(`${BOLD} ╔══════════════════════════════════════════╗${RESET}`);
|
|
348
552
|
log(`${BOLD} ║ ${GREEN}Setup complete!${RESET}${BOLD} ║${RESET}`);
|
|
349
553
|
log(`${BOLD} ╚══════════════════════════════════════════╝${RESET}`);
|
|
554
|
+
|
|
555
|
+
// Recap table
|
|
556
|
+
log("");
|
|
557
|
+
log(` ${BOLD}Pack status:${RESET}`);
|
|
558
|
+
for (const ps of packStatus) {
|
|
559
|
+
const icon = ps.active ? `${GREEN}✓${RESET}` : `${YELLOW}○${RESET}`;
|
|
560
|
+
const status = ps.active ? "ready" : "needs credentials";
|
|
561
|
+
log(` ${icon} ${ps.name} — ${status}`);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Packs not selected
|
|
565
|
+
const selectedIds = new Set(selectedPacks.map((p) => p.id));
|
|
566
|
+
const skippedPacks = PACKS.filter((p) => !selectedIds.has(p.id));
|
|
567
|
+
for (const sp of skippedPacks) {
|
|
568
|
+
log(` ${DIM}– ${sp.name} — not selected${RESET}`);
|
|
569
|
+
}
|
|
570
|
+
|
|
350
571
|
log("");
|
|
351
572
|
log(` ${BOLD}Next steps:${RESET}`);
|
|
352
573
|
log("");
|
|
353
574
|
log(` ${CYAN}cd ${projectName}${RESET}`);
|
|
354
|
-
if (!
|
|
355
|
-
log(` ${CYAN}npm run dev${RESET} ${DIM}# Start locally${RESET}`);
|
|
356
|
-
log(` ${CYAN}vercel${RESET} ${DIM}# Deploy to Vercel${RESET}`);
|
|
575
|
+
if (!deploySucceeded) {
|
|
576
|
+
log(` ${CYAN}npm run dev${RESET} ${DIM}# Start locally at http://localhost:3000${RESET}`);
|
|
577
|
+
log(` ${CYAN}vercel${RESET} ${DIM}# Deploy to Vercel when ready${RESET}`);
|
|
357
578
|
}
|
|
358
|
-
log(` ${
|
|
579
|
+
log(` ${DIM}Then visit: http://localhost:3000/setup${RESET}`);
|
|
359
580
|
log("");
|
|
360
581
|
log(` ${BOLD}Connect to Claude Desktop / Claude Code:${RESET}`);
|
|
361
582
|
log(` ${DIM}Endpoint: https://your-app.vercel.app/api/mcp${RESET}`);
|
|
362
|
-
log(` ${DIM}Token: ${
|
|
583
|
+
log(` ${DIM}Token: ${maskSecret(mcpToken)}${RESET}`);
|
|
363
584
|
log("");
|
|
364
585
|
log(` ${BOLD}Stay up to date:${RESET}`);
|
|
365
|
-
log(` ${CYAN}
|
|
586
|
+
log(` ${CYAN}npm run update${RESET}`);
|
|
366
587
|
log("");
|
|
367
588
|
|
|
368
589
|
rl.close();
|
package/package.json
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@yassinello/create-mymcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Set up your personal MCP server in minutes — interactive installer for MyMCP",
|
|
5
|
-
"bin": {
|
|
6
|
-
"create-mymcp": "./index.mjs"
|
|
7
|
-
},
|
|
8
|
-
"keywords": [
|
|
9
|
-
"mcp",
|
|
10
|
-
"create",
|
|
11
|
-
"installer",
|
|
12
|
-
"claude",
|
|
13
|
-
"chatgpt",
|
|
14
|
-
"ai",
|
|
15
|
-
"model-context-protocol"
|
|
16
|
-
],
|
|
17
|
-
"repository": {
|
|
18
|
-
"type": "git",
|
|
19
|
-
"url": "https://github.com/Yassinello/mymcp",
|
|
20
|
-
"directory": "create-mymcp"
|
|
21
|
-
},
|
|
22
|
-
"license": "MIT",
|
|
23
|
-
"engines": {
|
|
24
|
-
"node": ">=18"
|
|
25
|
-
},
|
|
26
|
-
"files": [
|
|
27
|
-
"index.mjs"
|
|
28
|
-
]
|
|
29
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@yassinello/create-mymcp",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Set up your personal MCP server in minutes — interactive installer for MyMCP",
|
|
5
|
+
"bin": {
|
|
6
|
+
"create-mymcp": "./index.mjs"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"mcp",
|
|
10
|
+
"create",
|
|
11
|
+
"installer",
|
|
12
|
+
"claude",
|
|
13
|
+
"chatgpt",
|
|
14
|
+
"ai",
|
|
15
|
+
"model-context-protocol"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/Yassinello/mymcp",
|
|
20
|
+
"directory": "create-mymcp"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"index.mjs"
|
|
28
|
+
]
|
|
29
|
+
}
|