@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.
- package/README.md +1 -1
- package/index.mjs +58 -29
- package/package.json +1 -1
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,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,
|
|
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 (
|
|
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
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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 `
|
|
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
|
-
|
|
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
|
-
|
|
302
|
+
writtenVars.add(match[1]);
|
|
273
303
|
} else {
|
|
274
304
|
envContent += line + "\n";
|
|
275
305
|
}
|
|
276
306
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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) + "..." : "(
|
|
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}
|
|
394
|
+
log(` ${CYAN}npm run update${RESET}`);
|
|
366
395
|
log("");
|
|
367
396
|
|
|
368
397
|
rl.close();
|