@tacitai/mcp-server 0.5.0 → 0.6.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/dist/setup.js +96 -23
- package/package.json +1 -1
package/dist/setup.js
CHANGED
|
@@ -33,6 +33,7 @@ import * as path from 'path';
|
|
|
33
33
|
import * as os from 'os';
|
|
34
34
|
import * as http from 'http';
|
|
35
35
|
import * as crypto from 'crypto';
|
|
36
|
+
import * as readline from 'readline';
|
|
36
37
|
import { execFile } from 'child_process';
|
|
37
38
|
import { TacitClient, TacitApiError } from './client.js';
|
|
38
39
|
function parseArgs(argv) {
|
|
@@ -314,6 +315,81 @@ async function deviceAuthFlow(apiUrl) {
|
|
|
314
315
|
});
|
|
315
316
|
});
|
|
316
317
|
}
|
|
318
|
+
function prompt(question, hidden = false) {
|
|
319
|
+
return new Promise((resolve) => {
|
|
320
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
321
|
+
if (hidden && process.stdin.isTTY) {
|
|
322
|
+
process.stderr.write(question);
|
|
323
|
+
const stdin = process.stdin;
|
|
324
|
+
stdin.setRawMode(true);
|
|
325
|
+
stdin.resume();
|
|
326
|
+
stdin.setEncoding('utf-8');
|
|
327
|
+
let input = '';
|
|
328
|
+
const onData = (ch) => {
|
|
329
|
+
if (ch === '\n' || ch === '\r' || ch === '\u0004') {
|
|
330
|
+
stdin.setRawMode(false);
|
|
331
|
+
stdin.pause();
|
|
332
|
+
stdin.removeListener('data', onData);
|
|
333
|
+
rl.close();
|
|
334
|
+
process.stderr.write('\n');
|
|
335
|
+
resolve(input);
|
|
336
|
+
}
|
|
337
|
+
else if (ch === '\u007F' || ch === '\b') {
|
|
338
|
+
if (input.length > 0)
|
|
339
|
+
input = input.slice(0, -1);
|
|
340
|
+
}
|
|
341
|
+
else if (ch === '\u0003') {
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
input += ch;
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
stdin.on('data', onData);
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
rl.question(question, (answer) => {
|
|
352
|
+
rl.close();
|
|
353
|
+
resolve(answer.trim());
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
async function loginFlow(apiUrl) {
|
|
359
|
+
process.stderr.write('\n Log in with your TACIT credentials.\n\n');
|
|
360
|
+
const email = await prompt(' Email: ');
|
|
361
|
+
const pin = await prompt(' PIN: ', true);
|
|
362
|
+
if (!email || !pin) {
|
|
363
|
+
process.stderr.write('Error: email and PIN are required.\n');
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
const loginUrl = apiUrl.endsWith('/api/v1')
|
|
367
|
+
? `${apiUrl}/auth/login`
|
|
368
|
+
: `${apiUrl}/api/v1/auth/login`;
|
|
369
|
+
const res = await fetch(loginUrl, {
|
|
370
|
+
method: 'POST',
|
|
371
|
+
headers: { 'Content-Type': 'application/json' },
|
|
372
|
+
body: JSON.stringify({ email, pin }),
|
|
373
|
+
});
|
|
374
|
+
if (!res.ok) {
|
|
375
|
+
const body = await res.text().catch(() => '');
|
|
376
|
+
if (res.status === 401) {
|
|
377
|
+
process.stderr.write('\n Login failed — check your email and PIN.\n');
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
process.stderr.write(`\n Login failed (${res.status}): ${body}\n`);
|
|
381
|
+
}
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
const data = await res.json();
|
|
385
|
+
const token = typeof data['session_token'] === 'string' ? data['session_token'] : '';
|
|
386
|
+
const workspaceName = typeof data['workspace_name'] === 'string' ? data['workspace_name'] : '';
|
|
387
|
+
if (!token) {
|
|
388
|
+
process.stderr.write('Error: login returned no session token.\n');
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
return { token, workspaceName };
|
|
392
|
+
}
|
|
317
393
|
// ─── Smoke synthesis ──────────────────────────────────────────────────────────
|
|
318
394
|
const SMOKE_QUERY = 'What is in this knowledge base?';
|
|
319
395
|
async function runSmoke(apiUrl, token) {
|
|
@@ -348,15 +424,8 @@ async function runSmoke(apiUrl, token) {
|
|
|
348
424
|
// ─── Main export ──────────────────────────────────────────────────────────────
|
|
349
425
|
export async function setup(argv) {
|
|
350
426
|
const opts = parseArgs(argv);
|
|
351
|
-
// Resolve api-url: flag > env.
|
|
352
|
-
const apiUrl = opts.apiUrl ?? process.env['TACIT_API_URL'] ??
|
|
353
|
-
if (!apiUrl) {
|
|
354
|
-
process.stderr.write('Error: no API URL configured.\n' +
|
|
355
|
-
'Provide --api-url <url> or set TACIT_API_URL environment variable.\n' +
|
|
356
|
-
'Example: npx @tacitai/mcp-server setup --api-url https://tacit-beta-prod.fly.dev --token <your-token>\n' +
|
|
357
|
-
'\nGet your token from: https://beta.tacitai.app → Settings → Bearer token\n');
|
|
358
|
-
process.exit(1);
|
|
359
|
-
}
|
|
427
|
+
// Resolve api-url: flag > env > default.
|
|
428
|
+
const apiUrl = opts.apiUrl ?? process.env['TACIT_API_URL'] ?? 'https://beta.tacitai.app';
|
|
360
429
|
// Determine profile and token.
|
|
361
430
|
let profile = opts.profile;
|
|
362
431
|
let token = opts.token;
|
|
@@ -365,23 +434,27 @@ export async function setup(argv) {
|
|
|
365
434
|
const envVarNameForProfile = profileToEnvVar(profile);
|
|
366
435
|
const envToken = process.env[envVarNameForProfile] ?? process.env['TACIT_TOKEN'] ?? null;
|
|
367
436
|
if (!token && !envToken && !opts.dryRun) {
|
|
368
|
-
// Neither --token nor env set and not a dry-run →
|
|
369
|
-
|
|
437
|
+
// Neither --token nor env set and not a dry-run → authenticate interactively.
|
|
438
|
+
// Try device-auth (browser flow) first; fall back to login prompt.
|
|
439
|
+
let authenticated = false;
|
|
370
440
|
try {
|
|
371
|
-
deviceResult = await deviceAuthFlow(apiUrl);
|
|
441
|
+
const deviceResult = await deviceAuthFlow(apiUrl);
|
|
442
|
+
token = deviceResult.token;
|
|
443
|
+
expiresAt = deviceResult.expiresAt;
|
|
444
|
+
if (!opts.profileExplicit && deviceResult.workspaceName) {
|
|
445
|
+
profile = workspaceNameToProfile(deviceResult.workspaceName);
|
|
446
|
+
}
|
|
447
|
+
authenticated = true;
|
|
372
448
|
}
|
|
373
|
-
catch
|
|
374
|
-
|
|
375
|
-
process.stderr.write(`Error during device authentication: ${msg}\n`);
|
|
376
|
-
process.exit(1);
|
|
377
|
-
return; // unreachable; satisfies TypeScript
|
|
449
|
+
catch {
|
|
450
|
+
// Device-auth not available — fall back to login prompt.
|
|
378
451
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
452
|
+
if (!authenticated) {
|
|
453
|
+
const loginResult = await loginFlow(apiUrl);
|
|
454
|
+
token = loginResult.token;
|
|
455
|
+
if (!opts.profileExplicit && loginResult.workspaceName) {
|
|
456
|
+
profile = workspaceNameToProfile(loginResult.workspaceName);
|
|
457
|
+
}
|
|
385
458
|
}
|
|
386
459
|
}
|
|
387
460
|
// Final env-var name derived from (possibly updated) profile.
|