@botuyo/mcp 0.1.0 → 0.2.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/dist/client.d.ts CHANGED
@@ -1,36 +1,31 @@
1
1
  /**
2
2
  * BotuyoApiClient - Thin HTTP wrapper around the BotUyo REST API
3
3
  * All MCP tools go through this client.
4
+ *
5
+ * Uses a JWT token directly (obtained via `npx @botuyo/mcp login`).
4
6
  */
5
7
  export interface BotuyoClientConfig {
6
8
  apiUrl: string;
7
- apiKey: string;
8
- }
9
- export interface AuthResult {
10
9
  token: string;
10
+ }
11
+ export interface AuthInfo {
11
12
  tenantId: string;
12
13
  tenantName: string;
13
14
  role: string;
14
- canWrite: boolean;
15
- channels: Array<{
16
- type: string;
17
- status: string;
18
- }>;
19
- adminPanelUrl: string;
15
+ email: string;
20
16
  }
21
17
  export declare class BotuyoApiClient {
22
18
  private config;
23
- private token;
24
- private authResult;
25
- private tokenExpiresAt;
19
+ private authInfo;
26
20
  constructor(config: BotuyoClientConfig);
27
- /** Exchange API key for a JWT and cache it for 55 minutes */
28
- authenticate(): Promise<AuthResult>;
21
+ /** Verify the stored JWT is still valid by calling GET /api/auth/me */
22
+ verify(): Promise<AuthInfo>;
29
23
  /** Make an authenticated GET request */
30
24
  get<T = any>(path: string): Promise<T>;
31
25
  /** Make an authenticated POST request */
32
26
  post<T = any>(path: string, data: unknown): Promise<T>;
33
27
  /** Make an authenticated PUT request */
34
28
  put<T = any>(path: string, data: unknown): Promise<T>;
29
+ private handleResponse;
35
30
  private parseJson;
36
31
  }
package/dist/client.js CHANGED
@@ -1,71 +1,74 @@
1
1
  /**
2
2
  * BotuyoApiClient - Thin HTTP wrapper around the BotUyo REST API
3
3
  * All MCP tools go through this client.
4
+ *
5
+ * Uses a JWT token directly (obtained via `npx @botuyo/mcp login`).
4
6
  */
5
7
  import fetch from 'node-fetch';
6
8
  export class BotuyoApiClient {
7
9
  config;
8
- token = null;
9
- authResult = null;
10
- tokenExpiresAt = 0;
10
+ authInfo = null;
11
11
  constructor(config) {
12
12
  this.config = config;
13
13
  }
14
- /** Exchange API key for a JWT and cache it for 55 minutes */
15
- async authenticate() {
16
- const now = Date.now();
17
- if (this.token && this.authResult && now < this.tokenExpiresAt) {
18
- return this.authResult;
19
- }
20
- const res = await fetch(`${this.config.apiUrl}/api/v1/mcp/auth`, {
21
- method: 'POST',
22
- headers: {
23
- 'x-api-key': this.config.apiKey,
24
- 'Content-Type': 'application/json'
25
- }
14
+ /** Verify the stored JWT is still valid by calling GET /api/auth/me */
15
+ async verify() {
16
+ if (this.authInfo)
17
+ return this.authInfo;
18
+ const res = await fetch(`${this.config.apiUrl}/api/auth/me`, {
19
+ headers: { Authorization: `Bearer ${this.config.token}` }
26
20
  });
27
21
  const body = await this.parseJson(res);
28
22
  if (!body.success) {
29
- throw new Error(`Authentication failed: ${body.error || 'Unknown error'}`);
23
+ throw new Error(`Session expired or invalid: ${body.error || 'Unknown error'}. Run: npx @botuyo/mcp login`);
30
24
  }
31
- this.token = body.data.token;
32
- this.authResult = body.data;
33
- this.tokenExpiresAt = now + 55 * 60 * 1000; // 55 min (token expires in 1h)
34
- return this.authResult;
25
+ const user = body.data.user;
26
+ this.authInfo = {
27
+ tenantId: user.tenantIds?.[0] || '',
28
+ tenantName: '', // Will be enriched from credentials
29
+ role: user.roles?.[0]?.role || 'member',
30
+ email: user.email
31
+ };
32
+ return this.authInfo;
35
33
  }
36
34
  /** Make an authenticated GET request */
37
35
  async get(path) {
38
- await this.authenticate();
39
36
  const res = await fetch(`${this.config.apiUrl}${path}`, {
40
- headers: { Authorization: `Bearer ${this.token}` }
37
+ headers: { Authorization: `Bearer ${this.config.token}` }
41
38
  });
42
- return this.parseJson(res);
39
+ return this.handleResponse(res);
43
40
  }
44
41
  /** Make an authenticated POST request */
45
42
  async post(path, data) {
46
- await this.authenticate();
47
43
  const res = await fetch(`${this.config.apiUrl}${path}`, {
48
44
  method: 'POST',
49
45
  headers: {
50
- Authorization: `Bearer ${this.token}`,
46
+ Authorization: `Bearer ${this.config.token}`,
51
47
  'Content-Type': 'application/json'
52
48
  },
53
49
  body: JSON.stringify(data)
54
50
  });
55
- return this.parseJson(res);
51
+ return this.handleResponse(res);
56
52
  }
57
53
  /** Make an authenticated PUT request */
58
54
  async put(path, data) {
59
- await this.authenticate();
60
55
  const res = await fetch(`${this.config.apiUrl}${path}`, {
61
56
  method: 'PUT',
62
57
  headers: {
63
- Authorization: `Bearer ${this.token}`,
58
+ Authorization: `Bearer ${this.config.token}`,
64
59
  'Content-Type': 'application/json'
65
60
  },
66
61
  body: JSON.stringify(data)
67
62
  });
68
- return this.parseJson(res);
63
+ return this.handleResponse(res);
64
+ }
65
+ async handleResponse(res) {
66
+ const json = await this.parseJson(res);
67
+ // Detect expired token
68
+ if (res.status === 401) {
69
+ throw new Error('Sesión expirada. Ejecutá: npx @botuyo/mcp login');
70
+ }
71
+ return json;
69
72
  }
70
73
  async parseJson(res) {
71
74
  const text = await res.text();
@@ -77,6 +80,8 @@ export class BotuyoApiClient {
77
80
  return json;
78
81
  }
79
82
  catch (e) {
83
+ if (e instanceof Error && e.message.includes('HTTP'))
84
+ throw e;
80
85
  throw new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`);
81
86
  }
82
87
  }
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @botuyo/mcp auth command (Browser OAuth flow)
4
+ *
5
+ * Usage: npx @botuyo/mcp auth
6
+ *
7
+ * Flow:
8
+ * 1. Starts a local HTTP server on a random port
9
+ * 2. Opens admin.botuyo.com/mcp-auth?redirect=http://localhost:PORT
10
+ * 3. The admin panel authenticates the user (login/Google),
11
+ * calls POST /api/v1/mcp/oauth/authorize, then redirects to localhost with ?code=...
12
+ * 4. CLI exchanges the code for an API key (POST /api/v1/mcp/oauth/token)
13
+ * 5. Uses the API key to get a JWT (POST /api/v1/mcp/auth)
14
+ * 6. Saves the JWT to ~/.botuyo/credentials.json
15
+ *
16
+ * For terminal-only login (email/password), use `npx @botuyo/mcp login` instead.
17
+ */
18
+ export declare function runAuth(args: string[]): Promise<void>;
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @botuyo/mcp auth command (Browser OAuth flow)
4
+ *
5
+ * Usage: npx @botuyo/mcp auth
6
+ *
7
+ * Flow:
8
+ * 1. Starts a local HTTP server on a random port
9
+ * 2. Opens admin.botuyo.com/mcp-auth?redirect=http://localhost:PORT
10
+ * 3. The admin panel authenticates the user (login/Google),
11
+ * calls POST /api/v1/mcp/oauth/authorize, then redirects to localhost with ?code=...
12
+ * 4. CLI exchanges the code for an API key (POST /api/v1/mcp/oauth/token)
13
+ * 5. Uses the API key to get a JWT (POST /api/v1/mcp/auth)
14
+ * 6. Saves the JWT to ~/.botuyo/credentials.json
15
+ *
16
+ * For terminal-only login (email/password), use `npx @botuyo/mcp login` instead.
17
+ */
18
+ import http from 'http';
19
+ import { URL } from 'url';
20
+ import { execSync } from 'child_process';
21
+ import { readCredentials, saveCredentials } from './credentials.js';
22
+ import os from 'os';
23
+ const ADMIN_URL = process.env.BOTUYO_ADMIN_URL || 'https://admin.botuyo.com';
24
+ const API_URL = process.env.BOTUYO_API_URL || 'https://api.botuyo.com';
25
+ export async function runAuth(args) {
26
+ console.log('\n🤖 BotUyo MCP — Autenticación por Browser\n');
27
+ const existing = await readCredentials();
28
+ if (existing && !args.includes('--force')) {
29
+ const expired = existing.expiresAt && new Date(existing.expiresAt) < new Date();
30
+ if (!expired) {
31
+ console.log(`✅ Ya estás autenticado como: ${existing.email || 'usuario'}`);
32
+ console.log(` Tenant: ${existing.tenantName} (${existing.role})`);
33
+ console.log('\n Usá --force para re-autenticarte.');
34
+ console.log(' O usá `npx @botuyo/mcp login` para login por terminal.\n');
35
+ return;
36
+ }
37
+ console.log('⚠️ Tu sesión expiró. Vamos a re-autenticarte.\n');
38
+ }
39
+ // Start local callback server
40
+ const { port, waitForCode } = await startCallbackServer();
41
+ const redirectUri = `http://localhost:${port}/callback`;
42
+ const deviceName = encodeURIComponent(`${os.hostname()} CLI`);
43
+ const authUrl = `${ADMIN_URL}/mcp-auth?redirect=${encodeURIComponent(redirectUri)}&device=${deviceName}`;
44
+ console.log(`📋 Abriendo browser para autorizar...\n ${authUrl}\n`);
45
+ openBrowser(authUrl);
46
+ console.log('Esperando autorización (10 minutos máximo)...\n');
47
+ const code = await waitForCode;
48
+ if (!code) {
49
+ console.error('❌ La autorización expiró o fue cancelada.');
50
+ process.exit(1);
51
+ }
52
+ console.log('✅ Código de autorización recibido! Intercambiando por credenciales...');
53
+ // Exchange code for API key
54
+ const tokenRes = await fetch(`${API_URL}/api/v1/mcp/oauth/token`, {
55
+ method: 'POST',
56
+ headers: { 'Content-Type': 'application/json' },
57
+ body: JSON.stringify({ code, name: `${os.hostname()} CLI` })
58
+ });
59
+ const tokenData = (await tokenRes.json());
60
+ if (!tokenData.success) {
61
+ console.error(`❌ Intercambio de token fallido: ${tokenData.error}`);
62
+ process.exit(1);
63
+ }
64
+ const apiKey = tokenData.data.key;
65
+ const tenantId = tokenData.data.tenantId;
66
+ const tenantName = tokenData.data.tenantName;
67
+ const role = tokenData.data.role;
68
+ // Now use the API key to get a JWT
69
+ console.log('⏳ Obteniendo sesión JWT...');
70
+ const authRes = await fetch(`${API_URL}/api/v1/mcp/auth`, {
71
+ method: 'POST',
72
+ headers: {
73
+ 'x-api-key': apiKey,
74
+ 'Content-Type': 'application/json'
75
+ }
76
+ });
77
+ const authData = (await authRes.json());
78
+ if (!authData.success) {
79
+ console.error(`❌ Error al obtener JWT: ${authData.error}`);
80
+ process.exit(1);
81
+ }
82
+ const creds = {
83
+ token: authData.data.token,
84
+ tenantId,
85
+ tenantName: tenantName || tenantId,
86
+ role: role || authData.data.role || 'viewer',
87
+ email: authData.data.callerEmail || '',
88
+ savedAt: new Date().toISOString(),
89
+ expiresAt: new Date(Date.now() + 1 * 60 * 60 * 1000).toISOString() // JWT from MCP auth = 1h
90
+ };
91
+ await saveCredentials(creds);
92
+ console.log(`\n✅ ¡Autenticado exitosamente!`);
93
+ console.log(` Tenant: ${creds.tenantName}`);
94
+ console.log(` Role: ${creds.role}`);
95
+ console.log(` Email: ${creds.email || '(no disponible)'}`);
96
+ console.log(`\n🚀 Ya podés usar el servidor MCP de BotUyo.\n`);
97
+ }
98
+ // ── Local callback server ─────────────────────────────────────────────────────
99
+ function startCallbackServer() {
100
+ return new Promise((resolve) => {
101
+ let resolveCode;
102
+ const waitForCode = new Promise((res) => {
103
+ resolveCode = res;
104
+ });
105
+ const server = http.createServer((req, res) => {
106
+ const url = new URL(req.url || '/', `http://localhost`);
107
+ if (url.pathname === '/callback') {
108
+ const code = url.searchParams.get('code');
109
+ const html = code
110
+ ? `<html><body style="font-family:sans-serif;text-align:center;padding:80px">
111
+ <h2>✅ BotUyo CLI Autorizado</h2>
112
+ <p>Podés cerrar esta pestaña y volver a la terminal.</p>
113
+ </body></html>`
114
+ : `<html><body style="font-family:sans-serif;text-align:center;padding:80px">
115
+ <h2>❌ Autorización fallida</h2>
116
+ <p>No se recibió el código. Intentá de nuevo.</p>
117
+ </body></html>`;
118
+ res.writeHead(200, { 'Content-Type': 'text/html' });
119
+ res.end(html);
120
+ server.close();
121
+ resolveCode(code);
122
+ }
123
+ else {
124
+ res.writeHead(404);
125
+ res.end();
126
+ }
127
+ });
128
+ server.listen(0, 'localhost', () => {
129
+ const port = server.address().port;
130
+ // Timeout after 10 minutes
131
+ setTimeout(() => { server.close(); resolveCode(null); }, 10 * 60 * 1000);
132
+ resolve({ port, waitForCode });
133
+ });
134
+ });
135
+ }
136
+ // ── Open browser cross-platform ───────────────────────────────────────────────
137
+ function openBrowser(url) {
138
+ const platform = process.platform;
139
+ try {
140
+ if (platform === 'win32')
141
+ execSync(`start "" "${url}"`, { stdio: 'ignore' });
142
+ else if (platform === 'darwin')
143
+ execSync(`open "${url}"`, { stdio: 'ignore' });
144
+ else
145
+ execSync(`xdg-open "${url}"`, { stdio: 'ignore' });
146
+ }
147
+ catch {
148
+ console.log(` No se pudo abrir el browser automáticamente. Visitá:\n ${url}\n`);
149
+ }
150
+ }
151
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Credential storage for @botuyo/mcp
3
+ * Saves/reads JWT tokens to/from ~/.botuyo/credentials.json
4
+ */
5
+ export interface BotuyoCredentials {
6
+ token: string;
7
+ tenantId: string;
8
+ tenantName: string;
9
+ role: string;
10
+ email: string;
11
+ savedAt: string;
12
+ expiresAt: string;
13
+ }
14
+ export declare function readCredentials(): Promise<BotuyoCredentials | null>;
15
+ export declare function saveCredentials(creds: BotuyoCredentials): Promise<void>;
16
+ export declare function clearCredentials(): Promise<void>;
17
+ /**
18
+ * Resolve the JWT token to use, in priority order:
19
+ * 1. BOTUYO_TOKEN env var
20
+ * 2. ~/.botuyo/credentials.json
21
+ *
22
+ * Returns null if no token or if expired.
23
+ */
24
+ export declare function resolveToken(): Promise<string | null>;
25
+ /**
26
+ * Check if stored credentials are expired
27
+ */
28
+ export declare function isTokenExpired(): Promise<boolean>;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Credential storage for @botuyo/mcp
3
+ * Saves/reads JWT tokens to/from ~/.botuyo/credentials.json
4
+ */
5
+ import { readFile, writeFile, mkdir, unlink } from 'fs/promises';
6
+ import { join } from 'path';
7
+ import { homedir } from 'os';
8
+ const CONFIG_DIR = join(homedir(), '.botuyo');
9
+ const CREDENTIALS_FILE = join(CONFIG_DIR, 'credentials.json');
10
+ export async function readCredentials() {
11
+ try {
12
+ const raw = await readFile(CREDENTIALS_FILE, 'utf8');
13
+ return JSON.parse(raw);
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
19
+ export async function saveCredentials(creds) {
20
+ await mkdir(CONFIG_DIR, { recursive: true });
21
+ await writeFile(CREDENTIALS_FILE, JSON.stringify(creds, null, 2), 'utf8');
22
+ }
23
+ export async function clearCredentials() {
24
+ try {
25
+ await unlink(CREDENTIALS_FILE);
26
+ }
27
+ catch {
28
+ // already gone
29
+ }
30
+ }
31
+ /**
32
+ * Resolve the JWT token to use, in priority order:
33
+ * 1. BOTUYO_TOKEN env var
34
+ * 2. ~/.botuyo/credentials.json
35
+ *
36
+ * Returns null if no token or if expired.
37
+ */
38
+ export async function resolveToken() {
39
+ if (process.env.BOTUYO_TOKEN)
40
+ return process.env.BOTUYO_TOKEN;
41
+ const creds = await readCredentials();
42
+ if (!creds?.token)
43
+ return null;
44
+ // Check if expired
45
+ if (creds.expiresAt && new Date(creds.expiresAt) < new Date()) {
46
+ return null; // expired
47
+ }
48
+ return creds.token;
49
+ }
50
+ /**
51
+ * Check if stored credentials are expired
52
+ */
53
+ export async function isTokenExpired() {
54
+ const creds = await readCredentials();
55
+ if (!creds?.expiresAt)
56
+ return true;
57
+ return new Date(creds.expiresAt) < new Date();
58
+ }
59
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/commands/credentials.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAA;AAE5B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAA;AAC7C,MAAM,gBAAgB,GAAG,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAA;AAY7D,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAA;QACpD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAsB,CAAA;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAAwB;IAC5D,MAAM,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC5C,MAAM,SAAS,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;AAC3E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAA;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAA;IAE7D,MAAM,KAAK,GAAG,MAAM,eAAe,EAAE,CAAA;IACrC,IAAI,CAAC,KAAK,EAAE,KAAK;QAAE,OAAO,IAAI,CAAA;IAE9B,mBAAmB;IACnB,IAAI,KAAK,CAAC,SAAS,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC;QAC9D,OAAO,IAAI,CAAA,CAAC,UAAU;IACxB,CAAC;IAED,OAAO,KAAK,CAAC,KAAK,CAAA;AACpB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,KAAK,GAAG,MAAM,eAAe,EAAE,CAAA;IACrC,IAAI,CAAC,KAAK,EAAE,SAAS;QAAE,OAAO,IAAI,CAAA;IAClC,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,IAAI,IAAI,EAAE,CAAA;AAC/C,CAAC"}
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @botuyo/mcp login command
4
+ *
5
+ * Usage: npx @botuyo/mcp login
6
+ *
7
+ * Flow:
8
+ * 1. Prompts for email and password in the terminal
9
+ * 2. Calls POST /api/auth/login to get a JWT
10
+ * 3. Calls GET /api/auth/me to get the user's tenants
11
+ * 4. If multiple tenants, asks the user to pick one
12
+ * 5. If needed, calls POST /api/auth/switch-tenant
13
+ * 6. Saves the credentials to ~/.botuyo/credentials.json
14
+ */
15
+ export declare function runLogin(args: string[]): Promise<void>;