@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.
Files changed (2) hide show
  1. package/dist/setup.js +96 -23
  2. 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'] ?? null;
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 → run automated device-auth flow.
369
- let deviceResult;
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 (err) {
374
- const msg = err instanceof Error ? err.message : String(err);
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
- // Token from device flow — use in memory only (NEVER log/write).
380
- token = deviceResult.token;
381
- expiresAt = deviceResult.expiresAt;
382
- // AC-12: derive profile from workspace_name unless --profile was explicit.
383
- if (!opts.profileExplicit && deviceResult.workspaceName) {
384
- profile = workspaceNameToProfile(deviceResult.workspaceName);
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tacitai/mcp-server",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "stdio MCP server wrapping Tacit /api/v1 REST/SSE API",
5
5
  "license": "MIT",
6
6
  "repository": {