@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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/index.mjs +274 -53
  3. package/package.json +29 -29
package/README.md CHANGED
@@ -5,7 +5,7 @@ Interactive installer for [MyMCP](https://github.com/Yassinello/mymcp) — your
5
5
  ## Usage
6
6
 
7
7
  ```bash
8
- npx create-mymcp@latest
8
+ npx @yassinello/create-mymcp@latest
9
9
  ```
10
10
 
11
11
  ## What it does
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 (owner/repo format)",
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, read, send, search (4 tools)",
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 (3 tools)",
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
- const dirInput = (
156
- await ask(` Project directory [${defaultDir}]: `)
157
- ).trim();
158
- const projectDir = resolve(dirInput || defaultDir);
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
- const files = run(`ls -A "${projectDir}"`);
163
- if (files) {
164
- log(` ${RED}✗${RESET} Directory "${projectName}" already exists and is not empty.`);
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 `git fetch upstream && git merge upstream/main` to pull updates");
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
- pack.id === "vault" || pack.id === "google"
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
- // Generate MCP auth token
224
- envVars.MCP_AUTH_TOKEN = randomBytes(32).toString("hex");
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 tz = (await ask(` Timezone [UTC]: `)).trim() || "UTC";
230
- const locale = (await ask(` Locale [en-US]: `)).trim() || "en-US";
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.optional) {
243
- info("(optional — press Enter to skip)");
244
- }
245
- const value = (await ask(` ${v.prompt}: `)).trim();
246
- if (value) {
247
- envVars[v.key] = value;
248
- ok(`${v.key} set`);
249
- } else if (!v.optional) {
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
- delete envVars[match[1]];
469
+ writtenVars.add(match[1]);
273
470
  } else {
274
471
  envContent += line + "\n";
275
472
  }
276
473
  }
277
- // Append any remaining vars not in .env.example
278
- for (const [key, value] of Object.entries(envVars)) {
279
- envContent += `${key}=${value}\n`;
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 (!deployVercel) {
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(` ${CYAN}open /setup${RESET} ${DIM}# Guided setup page${RESET}`);
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: ${envVars.MCP_AUTH_TOKEN ? envVars.MCP_AUTH_TOKEN.slice(0, 8) + "..." : "(in your .env)"}${RESET}`);
583
+ log(` ${DIM}Token: ${maskSecret(mcpToken)}${RESET}`);
363
584
  log("");
364
585
  log(` ${BOLD}Stay up to date:${RESET}`);
365
- log(` ${CYAN}git fetch upstream && git merge upstream/main${RESET}`);
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.1.1",
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
+ }