trapic-mcp 0.1.0 → 0.1.3
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/bin/trapic-mcp.mjs +113 -0
- package/dist/worker.js +77 -4
- package/package.json +1 -1
package/bin/trapic-mcp.mjs
CHANGED
|
@@ -379,9 +379,16 @@ async function cmdServeDev() {
|
|
|
379
379
|
}
|
|
380
380
|
|
|
381
381
|
async function cmdLogin() {
|
|
382
|
+
const useBrowser = process.argv.includes("--browser") || process.argv.includes("-b");
|
|
383
|
+
|
|
384
|
+
if (useBrowser) {
|
|
385
|
+
return cmdLoginBrowser();
|
|
386
|
+
}
|
|
387
|
+
|
|
382
388
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
383
389
|
|
|
384
390
|
try {
|
|
391
|
+
console.log("Login with email/password. For GitHub/Google, use: trapic login --browser\n");
|
|
385
392
|
const serverUrl =
|
|
386
393
|
(await rl.question(`Server URL [${DEFAULT_URL}]: `)).trim() || DEFAULT_URL;
|
|
387
394
|
const email = (await rl.question("Email: ")).trim();
|
|
@@ -483,6 +490,90 @@ async function cmdLogin() {
|
|
|
483
490
|
}
|
|
484
491
|
}
|
|
485
492
|
|
|
493
|
+
async function cmdLoginBrowser() {
|
|
494
|
+
const { exec } = await import("child_process");
|
|
495
|
+
|
|
496
|
+
console.log("Opening browser to create an API token...\n");
|
|
497
|
+
|
|
498
|
+
const tokenUrl = "https://trapic.ai/tokens";
|
|
499
|
+
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
500
|
+
exec(`${openCmd} ${tokenUrl}`);
|
|
501
|
+
|
|
502
|
+
console.log("1. Sign in with GitHub, Google, or email in the browser");
|
|
503
|
+
console.log("2. Create a new API token");
|
|
504
|
+
console.log("3. Copy the token and paste it below\n");
|
|
505
|
+
|
|
506
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
507
|
+
const token = (await rl.question("Paste your API token (tr_...): ")).trim();
|
|
508
|
+
rl.close();
|
|
509
|
+
|
|
510
|
+
if (!token || !token.startsWith("tr_")) {
|
|
511
|
+
console.error("Error: Invalid token. Must start with tr_");
|
|
512
|
+
process.exit(1);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Verify token
|
|
516
|
+
const res = await fetch("https://mcp.trapic.ai/auth/whoami", {
|
|
517
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
if (!res.ok) {
|
|
521
|
+
console.error("Error: Token verification failed. Check your token and try again.");
|
|
522
|
+
process.exit(1);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const data = await res.json();
|
|
526
|
+
const mcpUrl = "https://mcp.trapic.ai/mcp";
|
|
527
|
+
await saveCredentials({ url: mcpUrl, token, email: data.user?.email || "" });
|
|
528
|
+
console.log(`\nLogged in as ${data.user?.email || "unknown"}`);
|
|
529
|
+
console.log(`Credentials saved to ${CRED_FILE}`);
|
|
530
|
+
|
|
531
|
+
// Configure Claude Code
|
|
532
|
+
const rl2 = createInterface({ input: process.stdin, output: process.stdout });
|
|
533
|
+
try {
|
|
534
|
+
const setupClaude = (
|
|
535
|
+
await rl2.question("\nConfigure for Claude Code? [Y/n]: ")
|
|
536
|
+
).trim().toLowerCase();
|
|
537
|
+
|
|
538
|
+
if (setupClaude !== "n" && setupClaude !== "no") {
|
|
539
|
+
const scope = (
|
|
540
|
+
await rl2.question("Scope — global or project? [global]: ")
|
|
541
|
+
).trim().toLowerCase();
|
|
542
|
+
|
|
543
|
+
const mcpEntry = {
|
|
544
|
+
command: "npx",
|
|
545
|
+
args: ["-y", "trapic-mcp"],
|
|
546
|
+
env: { TRAPIC_TOKEN: token },
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
if (scope === "project" || scope === "p") {
|
|
550
|
+
const projectDir = process.cwd();
|
|
551
|
+
const claudeDir = join(projectDir, ".claude");
|
|
552
|
+
const claudeJson = join(claudeDir, "mcp.json");
|
|
553
|
+
await mkdir(claudeDir, { recursive: true });
|
|
554
|
+
let existing = {};
|
|
555
|
+
try { existing = JSON.parse(await readFile(claudeJson, "utf-8")); } catch {}
|
|
556
|
+
existing.mcpServers = existing.mcpServers || {};
|
|
557
|
+
existing.mcpServers.trapic = mcpEntry;
|
|
558
|
+
await writeFile(claudeJson, JSON.stringify(existing, null, 2) + "\n");
|
|
559
|
+
console.log(`Saved to ${claudeJson}`);
|
|
560
|
+
} else {
|
|
561
|
+
const globalDir = join(homedir(), ".claude");
|
|
562
|
+
const globalJson = join(globalDir, "mcp.json");
|
|
563
|
+
await mkdir(globalDir, { recursive: true });
|
|
564
|
+
let existing = {};
|
|
565
|
+
try { existing = JSON.parse(await readFile(globalJson, "utf-8")); } catch {}
|
|
566
|
+
existing.mcpServers = existing.mcpServers || {};
|
|
567
|
+
existing.mcpServers.trapic = mcpEntry;
|
|
568
|
+
await writeFile(globalJson, JSON.stringify(existing, null, 2) + "\n");
|
|
569
|
+
console.log(`Saved to ${globalJson}`);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
} finally {
|
|
573
|
+
rl2.close();
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
486
577
|
async function cmdLogout() {
|
|
487
578
|
const config = await loadConfig();
|
|
488
579
|
if (!config) {
|
|
@@ -873,12 +964,33 @@ switch (command) {
|
|
|
873
964
|
case "token":
|
|
874
965
|
await cmdToken(args[1]);
|
|
875
966
|
break;
|
|
967
|
+
case "upgrade":
|
|
968
|
+
case "update": {
|
|
969
|
+
const { execSync } = await import("child_process");
|
|
970
|
+
console.log("Checking for updates...");
|
|
971
|
+
try {
|
|
972
|
+
const latest = execSync("npm view trapic-mcp version", { encoding: "utf-8" }).trim();
|
|
973
|
+
const pkg = JSON.parse(await readFile(resolve(__dirname, "../package.json"), "utf-8"));
|
|
974
|
+
if (latest === pkg.version) {
|
|
975
|
+
console.log(`Already on latest version (${pkg.version})`);
|
|
976
|
+
} else {
|
|
977
|
+
console.log(`Upgrading ${pkg.version} → ${latest}...`);
|
|
978
|
+
execSync("npm install -g trapic-mcp@latest", { stdio: "inherit" });
|
|
979
|
+
console.log(`\nUpgraded to ${latest}`);
|
|
980
|
+
}
|
|
981
|
+
} catch (e) {
|
|
982
|
+
console.error("Upgrade failed:", e.message);
|
|
983
|
+
console.log("Try manually: npm install -g trapic-mcp@latest");
|
|
984
|
+
}
|
|
985
|
+
break;
|
|
986
|
+
}
|
|
876
987
|
default:
|
|
877
988
|
console.log(`trapic-mcp — CLI & MCP proxy for Trapic
|
|
878
989
|
|
|
879
990
|
Usage:
|
|
880
991
|
trapic-mcp Start stdio proxy (for Claude Code)
|
|
881
992
|
trapic-mcp login Login with email/password
|
|
993
|
+
trapic-mcp login --browser Login with GitHub/Google (opens browser)
|
|
882
994
|
trapic-mcp logout Revoke token and clear credentials
|
|
883
995
|
trapic-mcp whoami Show current user and token info
|
|
884
996
|
trapic-mcp init Manual setup with token (--global | --project)
|
|
@@ -888,6 +1000,7 @@ Usage:
|
|
|
888
1000
|
trapic-mcp token list List API tokens
|
|
889
1001
|
trapic-mcp token create [name] Create new API token
|
|
890
1002
|
trapic-mcp token revoke <id> Revoke an API token
|
|
1003
|
+
trapic-mcp upgrade Upgrade to latest version
|
|
891
1004
|
trapic-mcp serve:dev Start local dev MCP server
|
|
892
1005
|
|
|
893
1006
|
Environment:
|
package/dist/worker.js
CHANGED
|
@@ -110,7 +110,7 @@ function getCorsOrigin(request) {
|
|
|
110
110
|
function isValidRedirectUri(uri) {
|
|
111
111
|
try {
|
|
112
112
|
const parsed = new URL(uri);
|
|
113
|
-
const allowed = ["localhost", "127.0.0.1", "trapic.ai"];
|
|
113
|
+
const allowed = ["localhost", "127.0.0.1", "trapic.ai", "claude.ai"];
|
|
114
114
|
return allowed.some(h => parsed.hostname === h || parsed.hostname.endsWith("." + h));
|
|
115
115
|
}
|
|
116
116
|
catch {
|
|
@@ -249,8 +249,12 @@ export default {
|
|
|
249
249
|
label { display: block; font-size: 0.8rem; color: #a3a3a3; margin-bottom: 0.25rem; }
|
|
250
250
|
input[type="email"], input[type="password"] { width: 100%; padding: 0.6rem 0.75rem; background: #0a0a0a; border: 1px solid #333; border-radius: 8px; color: #e5e5e5; font-size: 0.9rem; margin-bottom: 1rem; outline: none; }
|
|
251
251
|
input:focus { border-color: #525252; }
|
|
252
|
-
button { width: 100%; padding: 0.6rem; background: #fff; color: #000; border: none; border-radius: 9999px; font-size: 0.9rem; font-weight: 600; cursor: pointer; }
|
|
253
|
-
button:hover { opacity: 0.9; }
|
|
252
|
+
button, .oauth-btn { width: 100%; padding: 0.6rem; background: #fff; color: #000; border: none; border-radius: 9999px; font-size: 0.9rem; font-weight: 600; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 0.5rem; text-decoration: none; }
|
|
253
|
+
button:hover, .oauth-btn:hover { opacity: 0.9; }
|
|
254
|
+
.oauth-btn { background: #262626; color: #e5e5e5; border: 1px solid #333; margin-bottom: 0.5rem; }
|
|
255
|
+
.divider { display: flex; align-items: center; gap: 0.75rem; margin: 1.25rem 0; }
|
|
256
|
+
.divider::before, .divider::after { content: ''; flex: 1; height: 1px; background: #333; }
|
|
257
|
+
.divider span { font-size: 0.75rem; color: #525252; }
|
|
254
258
|
.error { color: #ef4444; font-size: 0.8rem; margin-bottom: 1rem; }
|
|
255
259
|
</style>
|
|
256
260
|
</head>
|
|
@@ -259,6 +263,15 @@ export default {
|
|
|
259
263
|
<div class="logo">Trapic</div>
|
|
260
264
|
<div class="desc">Sign in to authorize access to your knowledge base</div>
|
|
261
265
|
${errorMsg ? `<div class="error">${escapeHtml(errorMsg)}</div>` : ""}
|
|
266
|
+
<a class="oauth-btn" href="${env.SUPABASE_URL}/auth/v1/authorize?provider=github&redirect_to=${encodeURIComponent(origin + '/oauth/callback-social?' + new URLSearchParams({ client_id: clientId, redirect_uri: redirectUri, state, code_challenge: codeChallenge, code_challenge_method: codeChallengeMethod }).toString())}">
|
|
267
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
|
|
268
|
+
Continue with GitHub
|
|
269
|
+
</a>
|
|
270
|
+
<a class="oauth-btn" href="${env.SUPABASE_URL}/auth/v1/authorize?provider=google&redirect_to=${encodeURIComponent(origin + '/oauth/callback-social?' + new URLSearchParams({ client_id: clientId, redirect_uri: redirectUri, state, code_challenge: codeChallenge, code_challenge_method: codeChallengeMethod }).toString())}">
|
|
271
|
+
<svg width="16" height="16" viewBox="0 0 24 24"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill="#4285F4"/><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/></svg>
|
|
272
|
+
Continue with Google
|
|
273
|
+
</a>
|
|
274
|
+
<div class="divider"><span>or</span></div>
|
|
262
275
|
<form method="POST" action="/oauth/authorize">
|
|
263
276
|
<input type="hidden" name="client_id" value="${escapeHtml(clientId)}" />
|
|
264
277
|
<input type="hidden" name="redirect_uri" value="${escapeHtml(redirectUri)}" />
|
|
@@ -266,7 +279,7 @@ export default {
|
|
|
266
279
|
<input type="hidden" name="code_challenge" value="${escapeHtml(codeChallenge)}" />
|
|
267
280
|
<input type="hidden" name="code_challenge_method" value="${escapeHtml(codeChallengeMethod)}" />
|
|
268
281
|
<label>Email</label>
|
|
269
|
-
<input type="email" name="email" required autocomplete="email"
|
|
282
|
+
<input type="email" name="email" required autocomplete="email" />
|
|
270
283
|
<label>Password</label>
|
|
271
284
|
<input type="password" name="password" required autocomplete="current-password" />
|
|
272
285
|
<button type="submit">Sign In</button>
|
|
@@ -283,6 +296,66 @@ export default {
|
|
|
283
296
|
},
|
|
284
297
|
});
|
|
285
298
|
}
|
|
299
|
+
// OAuth social callback — client-side page that reads hash fragment token
|
|
300
|
+
if (url.pathname === "/oauth/callback-social") {
|
|
301
|
+
// Serve a small HTML page that extracts access_token from hash and POSTs to server
|
|
302
|
+
const html = `<!DOCTYPE html><html><head><title>Authenticating...</title></head>
|
|
303
|
+
<body style="background:#0a0a0a;color:#e5e5e5;font-family:sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;">
|
|
304
|
+
<div style="text-align:center"><p>Authenticating...</p></div>
|
|
305
|
+
<script>
|
|
306
|
+
(function(){
|
|
307
|
+
var hash = window.location.hash.substring(1);
|
|
308
|
+
var params = new URLSearchParams(hash);
|
|
309
|
+
var accessToken = params.get('access_token');
|
|
310
|
+
var search = new URLSearchParams(window.location.search);
|
|
311
|
+
if (accessToken) {
|
|
312
|
+
// Forward to server endpoint with token in query
|
|
313
|
+
var url = '/oauth/complete-social?access_token=' + encodeURIComponent(accessToken)
|
|
314
|
+
+ '&client_id=' + encodeURIComponent(search.get('client_id') || '')
|
|
315
|
+
+ '&redirect_uri=' + encodeURIComponent(search.get('redirect_uri') || '')
|
|
316
|
+
+ '&state=' + encodeURIComponent(search.get('state') || '')
|
|
317
|
+
+ '&code_challenge=' + encodeURIComponent(search.get('code_challenge') || '')
|
|
318
|
+
+ '&code_challenge_method=' + encodeURIComponent(search.get('code_challenge_method') || '');
|
|
319
|
+
window.location.replace(url);
|
|
320
|
+
} else {
|
|
321
|
+
document.body.innerHTML = '<div style="text-align:center;padding:2rem;"><p>Authentication failed. Please try again.</p><a href="/" style="color:#60a5fa;">Go back</a></div>';
|
|
322
|
+
}
|
|
323
|
+
})();
|
|
324
|
+
</script></body></html>`;
|
|
325
|
+
return new Response(html, { headers: { "Content-Type": "text/html; charset=utf-8" } });
|
|
326
|
+
}
|
|
327
|
+
// OAuth social complete — server receives access_token, issues auth code
|
|
328
|
+
if (url.pathname === "/oauth/complete-social") {
|
|
329
|
+
const accessToken = url.searchParams.get("access_token") || "";
|
|
330
|
+
const clientId = url.searchParams.get("client_id") || "";
|
|
331
|
+
const redirectUri = url.searchParams.get("redirect_uri") || "";
|
|
332
|
+
const state = url.searchParams.get("state") || "";
|
|
333
|
+
const codeChallenge = url.searchParams.get("code_challenge") || "";
|
|
334
|
+
if (!redirectUri || !codeChallenge || !accessToken) {
|
|
335
|
+
return new Response("Missing parameters", { status: 400 });
|
|
336
|
+
}
|
|
337
|
+
if (!isValidRedirectUri(redirectUri)) {
|
|
338
|
+
return new Response("Invalid redirect_uri", { status: 400 });
|
|
339
|
+
}
|
|
340
|
+
const supabase = getSupabase();
|
|
341
|
+
const { data: { user: authUser }, error: authErr } = await supabase.auth.getUser(accessToken);
|
|
342
|
+
if (authErr || !authUser) {
|
|
343
|
+
const backUrl = new URL(`${origin}/oauth/authorize`);
|
|
344
|
+
backUrl.searchParams.set("client_id", clientId);
|
|
345
|
+
backUrl.searchParams.set("redirect_uri", redirectUri);
|
|
346
|
+
backUrl.searchParams.set("state", state);
|
|
347
|
+
backUrl.searchParams.set("code_challenge", codeChallenge);
|
|
348
|
+
backUrl.searchParams.set("code_challenge_method", "S256");
|
|
349
|
+
backUrl.searchParams.set("error", "Social login failed. Please try again.");
|
|
350
|
+
return new Response(null, { status: 302, headers: { "Location": backUrl.toString() } });
|
|
351
|
+
}
|
|
352
|
+
const code = await createAuthCode(env, authUser.id, codeChallenge, redirectUri);
|
|
353
|
+
const redirectUrl = new URL(redirectUri);
|
|
354
|
+
redirectUrl.searchParams.set("code", code);
|
|
355
|
+
if (state)
|
|
356
|
+
redirectUrl.searchParams.set("state", state);
|
|
357
|
+
return new Response(null, { status: 302, headers: { "Location": redirectUrl.toString() } });
|
|
358
|
+
}
|
|
286
359
|
// OAuth Authorize POST — validates credentials, issues auth code via 302 redirect
|
|
287
360
|
if (url.pathname === "/oauth/authorize" && request.method === "POST") {
|
|
288
361
|
// Parse form body (native form POST sends application/x-www-form-urlencoded)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trapic-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "AI knowledge management MCP server — automatically capture, search, and connect decisions, facts, and conventions across AI coding sessions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|