@yassinello/create-mymcp 0.1.1
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 +33 -0
- package/index.mjs +375 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# create-mymcp
|
|
2
|
+
|
|
3
|
+
Interactive installer for [MyMCP](https://github.com/Yassinello/mymcp) — your personal AI backend.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx create-mymcp@latest
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## What it does
|
|
12
|
+
|
|
13
|
+
1. **Clones** the MyMCP repo into a new directory
|
|
14
|
+
2. **Asks** which tool packs you want (Google Workspace, Obsidian, Browser, Slack, Notion)
|
|
15
|
+
3. **Collects** your API credentials interactively
|
|
16
|
+
4. **Generates** a `.env` file with your config
|
|
17
|
+
5. **Installs** dependencies
|
|
18
|
+
6. **Deploys** to Vercel (optional)
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- Node.js 18+
|
|
23
|
+
- git
|
|
24
|
+
|
|
25
|
+
## Staying up to date
|
|
26
|
+
|
|
27
|
+
The installer sets up an `upstream` remote automatically. To pull updates:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
git fetch upstream && git merge upstream/main
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Your `.env` is never overwritten — all config lives in env vars, not in code.
|
package/index.mjs
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// create-mymcp — Interactive installer for MyMCP
|
|
4
|
+
// Usage: npx create-mymcp@latest
|
|
5
|
+
|
|
6
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
7
|
+
import { createInterface } from "node:readline";
|
|
8
|
+
import { randomBytes } from "node:crypto";
|
|
9
|
+
import { existsSync, writeFileSync, readFileSync } from "node:fs";
|
|
10
|
+
import { join, resolve } from "node:path";
|
|
11
|
+
|
|
12
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
15
|
+
const ask = (q) => new Promise((r) => rl.question(q, r));
|
|
16
|
+
|
|
17
|
+
const BOLD = "\x1b[1m";
|
|
18
|
+
const DIM = "\x1b[2m";
|
|
19
|
+
const GREEN = "\x1b[32m";
|
|
20
|
+
const CYAN = "\x1b[36m";
|
|
21
|
+
const YELLOW = "\x1b[33m";
|
|
22
|
+
const RED = "\x1b[31m";
|
|
23
|
+
const RESET = "\x1b[0m";
|
|
24
|
+
|
|
25
|
+
const log = (msg) => console.log(msg);
|
|
26
|
+
const step = (n, msg) => log(`\n${CYAN}[${n}]${RESET} ${BOLD}${msg}${RESET}`);
|
|
27
|
+
const ok = (msg) => log(` ${GREEN}✓${RESET} ${msg}`);
|
|
28
|
+
const warn = (msg) => log(` ${YELLOW}!${RESET} ${msg}`);
|
|
29
|
+
const info = (msg) => log(` ${DIM}${msg}${RESET}`);
|
|
30
|
+
|
|
31
|
+
function run(cmd, opts = {}) {
|
|
32
|
+
try {
|
|
33
|
+
return execSync(cmd, { encoding: "utf-8", stdio: "pipe", ...opts }).trim();
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function hasCommand(cmd) {
|
|
40
|
+
const check = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
41
|
+
return run(check) !== null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function confirm(msg, defaultYes = true) {
|
|
45
|
+
const hint = defaultYes ? "Y/n" : "y/N";
|
|
46
|
+
const answer = (await ask(` ${msg} [${hint}] `)).trim().toLowerCase();
|
|
47
|
+
if (answer === "") return defaultYes;
|
|
48
|
+
return answer === "y" || answer === "yes";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Pack definitions ─────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
const PACKS = [
|
|
54
|
+
{
|
|
55
|
+
id: "google",
|
|
56
|
+
name: "Google Workspace",
|
|
57
|
+
tools: "Gmail, Calendar, Contacts, Drive (18 tools)",
|
|
58
|
+
vars: [
|
|
59
|
+
{
|
|
60
|
+
key: "GOOGLE_CLIENT_ID",
|
|
61
|
+
prompt: "Google OAuth Client ID",
|
|
62
|
+
help: "https://console.cloud.google.com/apis/credentials",
|
|
63
|
+
},
|
|
64
|
+
{ key: "GOOGLE_CLIENT_SECRET", prompt: "Google OAuth Client Secret" },
|
|
65
|
+
{
|
|
66
|
+
key: "GOOGLE_REFRESH_TOKEN",
|
|
67
|
+
prompt: "Google OAuth Refresh Token",
|
|
68
|
+
help: "Run the OAuth flow after deploy at /api/auth/google",
|
|
69
|
+
optional: true,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "vault",
|
|
75
|
+
name: "Obsidian Vault",
|
|
76
|
+
tools: "Read, write, search, backlinks, web clipper (15 tools)",
|
|
77
|
+
vars: [
|
|
78
|
+
{
|
|
79
|
+
key: "GITHUB_PAT",
|
|
80
|
+
prompt: "GitHub PAT (with repo scope)",
|
|
81
|
+
help: "https://github.com/settings/tokens",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
key: "GITHUB_REPO",
|
|
85
|
+
prompt: "GitHub repo (owner/repo format)",
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "browser",
|
|
91
|
+
name: "Browser Automation",
|
|
92
|
+
tools: "Web browse, extract, act, LinkedIn feed (4 tools)",
|
|
93
|
+
vars: [
|
|
94
|
+
{
|
|
95
|
+
key: "BROWSERBASE_API_KEY",
|
|
96
|
+
prompt: "Browserbase API key",
|
|
97
|
+
help: "https://browserbase.com",
|
|
98
|
+
},
|
|
99
|
+
{ key: "BROWSERBASE_PROJECT_ID", prompt: "Browserbase Project ID" },
|
|
100
|
+
{
|
|
101
|
+
key: "OPENROUTER_API_KEY",
|
|
102
|
+
prompt: "OpenRouter API key",
|
|
103
|
+
help: "https://openrouter.ai/keys",
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: "slack",
|
|
109
|
+
name: "Slack",
|
|
110
|
+
tools: "Channels, read, send, search (4 tools)",
|
|
111
|
+
vars: [
|
|
112
|
+
{
|
|
113
|
+
key: "SLACK_BOT_TOKEN",
|
|
114
|
+
prompt: "Slack Bot User OAuth Token",
|
|
115
|
+
help: "https://api.slack.com/apps → OAuth & Permissions",
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "notion",
|
|
121
|
+
name: "Notion",
|
|
122
|
+
tools: "Search, read, create (3 tools)",
|
|
123
|
+
vars: [
|
|
124
|
+
{
|
|
125
|
+
key: "NOTION_API_KEY",
|
|
126
|
+
prompt: "Notion Internal Integration Token",
|
|
127
|
+
help: "https://www.notion.so/my-integrations",
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
// ── Main ─────────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
async function main() {
|
|
136
|
+
log("");
|
|
137
|
+
log(
|
|
138
|
+
`${BOLD} ╔══════════════════════════════════════════╗${RESET}`
|
|
139
|
+
);
|
|
140
|
+
log(
|
|
141
|
+
`${BOLD} ║ ${CYAN}create-mymcp${RESET}${BOLD} ║${RESET}`
|
|
142
|
+
);
|
|
143
|
+
log(
|
|
144
|
+
`${BOLD} ║ Your personal AI backend in minutes ║${RESET}`
|
|
145
|
+
);
|
|
146
|
+
log(
|
|
147
|
+
`${BOLD} ╚══════════════════════════════════════════╝${RESET}`
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// ── Step 1: Project directory ────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
step("1/5", "Project setup");
|
|
153
|
+
|
|
154
|
+
const defaultDir = "mymcp";
|
|
155
|
+
const dirInput = (
|
|
156
|
+
await ask(` Project directory [${defaultDir}]: `)
|
|
157
|
+
).trim();
|
|
158
|
+
const projectDir = resolve(dirInput || defaultDir);
|
|
159
|
+
const projectName = projectDir.split(/[/\\]/).pop();
|
|
160
|
+
|
|
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
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Step 2: Clone ────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
step("2/5", "Cloning MyMCP");
|
|
173
|
+
|
|
174
|
+
if (!hasCommand("git")) {
|
|
175
|
+
log(` ${RED}✗${RESET} git is required. Install it from https://git-scm.com`);
|
|
176
|
+
rl.close();
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const cloneResult = spawnSync(
|
|
181
|
+
"git",
|
|
182
|
+
["clone", "https://github.com/Yassinello/mymcp.git", projectDir],
|
|
183
|
+
{ stdio: "inherit" }
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
if (cloneResult.status !== 0) {
|
|
187
|
+
log(` ${RED}✗${RESET} Failed to clone repository.`);
|
|
188
|
+
rl.close();
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Set up upstream remote for easy updates
|
|
193
|
+
run(`git -C "${projectDir}" remote rename origin upstream`);
|
|
194
|
+
ok("Cloned and upstream remote configured");
|
|
195
|
+
info("Run `git fetch upstream && git merge upstream/main` to pull updates");
|
|
196
|
+
|
|
197
|
+
// ── Step 3: Pick packs ───────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
step("3/5", "Choose your tool packs");
|
|
200
|
+
log("");
|
|
201
|
+
|
|
202
|
+
const selectedPacks = [];
|
|
203
|
+
for (const pack of PACKS) {
|
|
204
|
+
const yes = await confirm(
|
|
205
|
+
`${BOLD}${pack.name}${RESET} — ${pack.tools}?`,
|
|
206
|
+
pack.id === "vault" || pack.id === "google"
|
|
207
|
+
);
|
|
208
|
+
if (yes) selectedPacks.push(pack);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (selectedPacks.length === 0) {
|
|
212
|
+
warn("No packs selected. You can add them later in your .env file.");
|
|
213
|
+
} else {
|
|
214
|
+
ok(`Selected: ${selectedPacks.map((p) => p.name).join(", ")}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ── Step 4: Collect credentials ──────────────────────────────────
|
|
218
|
+
|
|
219
|
+
step("4/5", "Configure credentials");
|
|
220
|
+
|
|
221
|
+
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)}...`);
|
|
226
|
+
|
|
227
|
+
// Instance settings
|
|
228
|
+
log("");
|
|
229
|
+
const tz = (await ask(` Timezone [UTC]: `)).trim() || "UTC";
|
|
230
|
+
const locale = (await ask(` Locale [en-US]: `)).trim() || "en-US";
|
|
231
|
+
const displayName = (await ask(` Display name [User]: `)).trim() || "User";
|
|
232
|
+
envVars.MYMCP_TIMEZONE = tz;
|
|
233
|
+
envVars.MYMCP_LOCALE = locale;
|
|
234
|
+
envVars.MYMCP_DISPLAY_NAME = displayName;
|
|
235
|
+
|
|
236
|
+
// Pack credentials
|
|
237
|
+
for (const pack of selectedPacks) {
|
|
238
|
+
log("");
|
|
239
|
+
log(` ${BOLD}${pack.name}${RESET}`);
|
|
240
|
+
for (const v of pack.vars) {
|
|
241
|
+
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) {
|
|
250
|
+
warn(`${v.key} skipped — ${pack.name} pack won't activate until set`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ── Write .env ───────────────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
const envPath = join(projectDir, ".env");
|
|
258
|
+
const envExamplePath = join(projectDir, ".env.example");
|
|
259
|
+
|
|
260
|
+
// Read .env.example as base, then overlay collected values
|
|
261
|
+
let envContent = "# MyMCP — Generated by create-mymcp\n";
|
|
262
|
+
envContent += `# Created: ${new Date().toISOString().split("T")[0]}\n\n`;
|
|
263
|
+
|
|
264
|
+
if (existsSync(envExamplePath)) {
|
|
265
|
+
const example = readFileSync(envExamplePath, "utf-8");
|
|
266
|
+
// Parse .env.example and fill in values we collected
|
|
267
|
+
const lines = example.split("\n");
|
|
268
|
+
for (const line of lines) {
|
|
269
|
+
const match = line.match(/^([A-Z_]+)=(.*)$/);
|
|
270
|
+
if (match && envVars[match[1]] !== undefined) {
|
|
271
|
+
envContent += `${match[1]}=${envVars[match[1]]}\n`;
|
|
272
|
+
delete envVars[match[1]];
|
|
273
|
+
} else {
|
|
274
|
+
envContent += line + "\n";
|
|
275
|
+
}
|
|
276
|
+
}
|
|
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)) {
|
|
283
|
+
envContent += `${key}=${value}\n`;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
writeFileSync(envPath, envContent);
|
|
288
|
+
ok(".env file created");
|
|
289
|
+
|
|
290
|
+
// ── Step 5: Install & Deploy ─────────────────────────────────────
|
|
291
|
+
|
|
292
|
+
step("5/5", "Install & deploy");
|
|
293
|
+
|
|
294
|
+
// Install dependencies
|
|
295
|
+
log("");
|
|
296
|
+
info("Installing dependencies...");
|
|
297
|
+
const installResult = spawnSync("npm", ["install"], {
|
|
298
|
+
cwd: projectDir,
|
|
299
|
+
stdio: "inherit",
|
|
300
|
+
shell: true,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
if (installResult.status !== 0) {
|
|
304
|
+
warn("npm install failed — you can run it manually later");
|
|
305
|
+
} else {
|
|
306
|
+
ok("Dependencies installed");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Offer Vercel deploy
|
|
310
|
+
log("");
|
|
311
|
+
const deployVercel = await confirm(
|
|
312
|
+
"Deploy to Vercel now? (requires Vercel CLI)",
|
|
313
|
+
false
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
if (deployVercel) {
|
|
317
|
+
if (!hasCommand("vercel")) {
|
|
318
|
+
info("Installing Vercel CLI...");
|
|
319
|
+
spawnSync("npm", ["install", "-g", "vercel"], {
|
|
320
|
+
stdio: "inherit",
|
|
321
|
+
shell: true,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
log("");
|
|
326
|
+
info("Running vercel deploy...");
|
|
327
|
+
const vercelResult = spawnSync("vercel", ["--yes"], {
|
|
328
|
+
cwd: projectDir,
|
|
329
|
+
stdio: "inherit",
|
|
330
|
+
shell: true,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
if (vercelResult.status === 0) {
|
|
334
|
+
ok("Deployed to Vercel!");
|
|
335
|
+
log("");
|
|
336
|
+
warn("Don't forget to add your env vars in the Vercel dashboard:");
|
|
337
|
+
info("Vercel → Project Settings → Environment Variables");
|
|
338
|
+
info("Or run: vercel env add MCP_AUTH_TOKEN");
|
|
339
|
+
} else {
|
|
340
|
+
warn("Deploy failed — you can run `vercel` manually in your project dir");
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ── Done ─────────────────────────────────────────────────────────
|
|
345
|
+
|
|
346
|
+
log("");
|
|
347
|
+
log(`${BOLD} ╔══════════════════════════════════════════╗${RESET}`);
|
|
348
|
+
log(`${BOLD} ║ ${GREEN}Setup complete!${RESET}${BOLD} ║${RESET}`);
|
|
349
|
+
log(`${BOLD} ╚══════════════════════════════════════════╝${RESET}`);
|
|
350
|
+
log("");
|
|
351
|
+
log(` ${BOLD}Next steps:${RESET}`);
|
|
352
|
+
log("");
|
|
353
|
+
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}`);
|
|
357
|
+
}
|
|
358
|
+
log(` ${CYAN}open /setup${RESET} ${DIM}# Guided setup page${RESET}`);
|
|
359
|
+
log("");
|
|
360
|
+
log(` ${BOLD}Connect to Claude Desktop / Claude Code:${RESET}`);
|
|
361
|
+
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}`);
|
|
363
|
+
log("");
|
|
364
|
+
log(` ${BOLD}Stay up to date:${RESET}`);
|
|
365
|
+
log(` ${CYAN}git fetch upstream && git merge upstream/main${RESET}`);
|
|
366
|
+
log("");
|
|
367
|
+
|
|
368
|
+
rl.close();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
main().catch((err) => {
|
|
372
|
+
console.error(`\n${RED}Error:${RESET} ${err.message}`);
|
|
373
|
+
rl.close();
|
|
374
|
+
process.exit(1);
|
|
375
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +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
|
+
}
|