@yassinello/create-mymcp 0.1.1 → 0.2.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 +58 -29
  3. package/package.json +1 -1
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,21 @@ async function confirm(msg, defaultYes = true) {
48
48
  return answer === "y" || answer === "yes";
49
49
  }
50
50
 
51
+ /** Clean user input: strip surrounding quotes, 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
+ const entries = readdirSync(dir);
60
+ return entries.length > 0;
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+
51
66
  // ── Pack definitions ─────────────────────────────────────────────────
52
67
 
53
68
  const PACKS = [
@@ -107,7 +122,7 @@ const PACKS = [
107
122
  {
108
123
  id: "slack",
109
124
  name: "Slack",
110
- tools: "Channels, read, send, search (4 tools)",
125
+ tools: "Channels, messages, threads, profiles, search (6 tools)",
111
126
  vars: [
112
127
  {
113
128
  key: "SLACK_BOT_TOKEN",
@@ -119,7 +134,7 @@ const PACKS = [
119
134
  {
120
135
  id: "notion",
121
136
  name: "Notion",
122
- tools: "Search, read, create (3 tools)",
137
+ tools: "Search, read, create, update, query databases (5 tools)",
123
138
  vars: [
124
139
  {
125
140
  key: "NOTION_API_KEY",
@@ -128,6 +143,18 @@ const PACKS = [
128
143
  },
129
144
  ],
130
145
  },
146
+ {
147
+ id: "composio",
148
+ name: "Composio",
149
+ tools: "1000+ app integrations — Jira, HubSpot, Salesforce, Airtable... (2 tools)",
150
+ vars: [
151
+ {
152
+ key: "COMPOSIO_API_KEY",
153
+ prompt: "Composio API key",
154
+ help: "https://composio.dev → Settings",
155
+ },
156
+ ],
157
+ },
131
158
  ];
132
159
 
133
160
  // ── Main ─────────────────────────────────────────────────────────────
@@ -152,21 +179,21 @@ async function main() {
152
179
  step("1/5", "Project setup");
153
180
 
154
181
  const defaultDir = "mymcp";
155
- const dirInput = (
156
- await ask(` Project directory [${defaultDir}]: `)
157
- ).trim();
158
- const projectDir = resolve(dirInput || defaultDir);
182
+ const rawInput = (await ask(` Project directory [${defaultDir}]: `)).trim();
183
+ const cleaned = cleanPath(rawInput) || defaultDir;
184
+
185
+ // If it's already absolute, use as-is; otherwise resolve relative to CWD
186
+ const projectDir = isAbsolute(cleaned) ? cleaned : resolve(cleaned);
159
187
  const projectName = projectDir.split(/[/\\]/).pop();
160
188
 
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
- }
189
+ if (existsSync(projectDir) && isDirNonEmpty(projectDir)) {
190
+ log(` ${RED}✗${RESET} Directory "${projectDir}" already exists and is not empty.`);
191
+ rl.close();
192
+ process.exit(1);
168
193
  }
169
194
 
195
+ info(`Will create: ${projectDir}`);
196
+
170
197
  // ── Step 2: Clone ────────────────────────────────────────────────
171
198
 
172
199
  step("2/5", "Cloning MyMCP");
@@ -192,7 +219,7 @@ async function main() {
192
219
  // Set up upstream remote for easy updates
193
220
  run(`git -C "${projectDir}" remote rename origin upstream`);
194
221
  ok("Cloned and upstream remote configured");
195
- info("Run `git fetch upstream && git merge upstream/main` to pull updates");
222
+ info("Run `npm run update` to pull updates anytime");
196
223
 
197
224
  // ── Step 3: Pick packs ───────────────────────────────────────────
198
225
 
@@ -201,9 +228,10 @@ async function main() {
201
228
 
202
229
  const selectedPacks = [];
203
230
  for (const pack of PACKS) {
231
+ const defaultOn = pack.id === "vault" || pack.id === "google";
204
232
  const yes = await confirm(
205
233
  `${BOLD}${pack.name}${RESET} — ${pack.tools}?`,
206
- pack.id === "vault" || pack.id === "google"
234
+ defaultOn
207
235
  );
208
236
  if (yes) selectedPacks.push(pack);
209
237
  }
@@ -261,25 +289,26 @@ async function main() {
261
289
  let envContent = "# MyMCP — Generated by create-mymcp\n";
262
290
  envContent += `# Created: ${new Date().toISOString().split("T")[0]}\n\n`;
263
291
 
292
+ // Track which vars we've written so we don't duplicate
293
+ const writtenVars = new Set();
294
+
264
295
  if (existsSync(envExamplePath)) {
265
296
  const example = readFileSync(envExamplePath, "utf-8");
266
- // Parse .env.example and fill in values we collected
267
297
  const lines = example.split("\n");
268
298
  for (const line of lines) {
269
- const match = line.match(/^([A-Z_]+)=(.*)$/);
299
+ const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
270
300
  if (match && envVars[match[1]] !== undefined) {
271
301
  envContent += `${match[1]}=${envVars[match[1]]}\n`;
272
- delete envVars[match[1]];
302
+ writtenVars.add(match[1]);
273
303
  } else {
274
304
  envContent += line + "\n";
275
305
  }
276
306
  }
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)) {
307
+ }
308
+
309
+ // Append any remaining vars not in .env.example
310
+ for (const [key, value] of Object.entries(envVars)) {
311
+ if (!writtenVars.has(key)) {
283
312
  envContent += `${key}=${value}\n`;
284
313
  }
285
314
  }
@@ -359,10 +388,10 @@ async function main() {
359
388
  log("");
360
389
  log(` ${BOLD}Connect to Claude Desktop / Claude Code:${RESET}`);
361
390
  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}`);
391
+ log(` ${DIM}Token: starts with ${envVars.MCP_AUTH_TOKEN ? envVars.MCP_AUTH_TOKEN.slice(0, 8) + "..." : "(check your .env)"}${RESET}`);
363
392
  log("");
364
393
  log(` ${BOLD}Stay up to date:${RESET}`);
365
- log(` ${CYAN}git fetch upstream && git merge upstream/main${RESET}`);
394
+ log(` ${CYAN}npm run update${RESET}`);
366
395
  log("");
367
396
 
368
397
  rl.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yassinello/create-mymcp",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Set up your personal MCP server in minutes — interactive installer for MyMCP",
5
5
  "bin": {
6
6
  "create-mymcp": "./index.mjs"