@selfhost.dev/mcp-server 0.1.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 (59) hide show
  1. package/README.md +382 -0
  2. package/dist/auth.d.ts +18 -0
  3. package/dist/auth.js +204 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/client.d.ts +10 -0
  6. package/dist/client.js +84 -0
  7. package/dist/client.js.map +1 -0
  8. package/dist/config.d.ts +13 -0
  9. package/dist/config.js +20 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +71 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/rate-limiter.d.ts +22 -0
  15. package/dist/rate-limiter.js +124 -0
  16. package/dist/rate-limiter.js.map +1 -0
  17. package/dist/session.d.ts +25 -0
  18. package/dist/session.js +48 -0
  19. package/dist/session.js.map +1 -0
  20. package/dist/tools/alerts.d.ts +2 -0
  21. package/dist/tools/alerts.js +372 -0
  22. package/dist/tools/alerts.js.map +1 -0
  23. package/dist/tools/auth.d.ts +2 -0
  24. package/dist/tools/auth.js +142 -0
  25. package/dist/tools/auth.js.map +1 -0
  26. package/dist/tools/backups.d.ts +2 -0
  27. package/dist/tools/backups.js +294 -0
  28. package/dist/tools/backups.js.map +1 -0
  29. package/dist/tools/credentials.d.ts +2 -0
  30. package/dist/tools/credentials.js +187 -0
  31. package/dist/tools/credentials.js.map +1 -0
  32. package/dist/tools/instances.d.ts +2 -0
  33. package/dist/tools/instances.js +555 -0
  34. package/dist/tools/instances.js.map +1 -0
  35. package/dist/tools/networking.d.ts +2 -0
  36. package/dist/tools/networking.js +240 -0
  37. package/dist/tools/networking.js.map +1 -0
  38. package/dist/tools/organizations.d.ts +2 -0
  39. package/dist/tools/organizations.js +260 -0
  40. package/dist/tools/organizations.js.map +1 -0
  41. package/dist/tools/pg-config.d.ts +2 -0
  42. package/dist/tools/pg-config.js +59 -0
  43. package/dist/tools/pg-config.js.map +1 -0
  44. package/dist/types/api.d.ts +20 -0
  45. package/dist/types/api.js +2 -0
  46. package/dist/types/api.js.map +1 -0
  47. package/dist/types/models.d.ts +44 -0
  48. package/dist/types/models.js +3 -0
  49. package/dist/types/models.js.map +1 -0
  50. package/dist/types/tiers.d.ts +15 -0
  51. package/dist/types/tiers.js +91 -0
  52. package/dist/types/tiers.js.map +1 -0
  53. package/dist/utils/formatters.d.ts +1 -0
  54. package/dist/utils/formatters.js +28 -0
  55. package/dist/utils/formatters.js.map +1 -0
  56. package/dist/utils/validators.d.ts +14 -0
  57. package/dist/utils/validators.js +9 -0
  58. package/dist/utils/validators.js.map +1 -0
  59. package/package.json +51 -0
package/README.md ADDED
@@ -0,0 +1,382 @@
1
+ # SelfHost MCP Server
2
+
3
+ A full-featured [Model Context Protocol](https://modelcontextprotocol.io/) server that brings [SelfHost's](https://selfhost.dev) database management capabilities directly into AI coding assistants. Provision, monitor, scale, and manage production database infrastructure — all through natural language, without leaving your editor.
4
+
5
+ Works with **Claude Code**, **Cursor**, **Windsurf**, **Cline**, and any MCP-compatible client.
6
+
7
+ > **JIRA Epic:** [SELFHOST-110](https://selfhost.atlassian.net/browse/SELFHOST-110)
8
+
9
+ ---
10
+
11
+ ## Quick Start
12
+
13
+ ### Prerequisites
14
+
15
+ - A [SelfHost.dev](https://selfhost.dev) account (invite-only — ask an existing user or join the waitlist)
16
+ - **Node.js 18+** or **Bun**
17
+
18
+ ### Install
19
+
20
+ **Claude Code:**
21
+
22
+ ```bash
23
+ # Using npm (Node.js)
24
+ claude mcp add selfhost -- npx @selfhost.dev/mcp-server
25
+
26
+ # Using Bun
27
+ claude mcp add selfhost -- bunx @selfhost.dev/mcp-server
28
+ ```
29
+
30
+ **Cursor / Windsurf / Other MCP clients:**
31
+
32
+ Add to your MCP configuration file:
33
+
34
+ ```json
35
+ {
36
+ "mcpServers": {
37
+ "selfhost": {
38
+ "command": "npx",
39
+ "args": ["@selfhost.dev/mcp-server"]
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ Or with Bun:
46
+
47
+ ```json
48
+ {
49
+ "mcpServers": {
50
+ "selfhost": {
51
+ "command": "bunx",
52
+ "args": ["@selfhost.dev/mcp-server"]
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ That's it. The server handles authentication, token refresh, and everything else automatically.
59
+
60
+ ### First Run
61
+
62
+ On first use, the MCP server opens your browser to sign in via SelfHost's console. After you authorize, credentials are saved locally (`~/.selfhost/credentials.json`) and auto-refreshed — you won't need to sign in again.
63
+
64
+ ```
65
+ You: "Show me my databases"
66
+
67
+ Claude: Authenticates → fetches your orgs → selects org → lists instances
68
+ → Shows you a summary of all your running databases
69
+ ```
70
+
71
+ ---
72
+
73
+ ## What Can You Do?
74
+
75
+ ### Talk to your infrastructure in plain English
76
+
77
+ | You say | What happens |
78
+ |---------|-------------|
79
+ | *"Spin up a Postgres 18 instance in us-east-1"* | Walks you through config, shows cost estimate, provisions it |
80
+ | *"Stop my staging database"* | Finds the instance, confirms with you, stops it |
81
+ | *"Set up a CPU alert at 80% on my prod DB"* | Creates an alert rule with email notification |
82
+ | *"Show me the backups for my main instance"* | Lists all backups with sizes and timestamps |
83
+ | *"Fork my production database for testing"* | Creates an independent clone with a new password |
84
+ | *"What's my estimated monthly cost for an r6i.xlarge with 500GB gp3?"* | Calculates itemized cost breakdown |
85
+ | *"Tune shared_buffers on my instance"* | Previews defaults, applies the override, syncs to replicas |
86
+
87
+ ### Safety Built In
88
+
89
+ Destructive operations (delete, stop, reboot) require explicit confirmation. The server will always ask before proceeding — you'll never accidentally delete a production database.
90
+
91
+ ---
92
+
93
+ ## Features
94
+
95
+ ### 76 tools across 8 modules
96
+
97
+ Every tool maps to a real API endpoint. No mock data, no shortcuts.
98
+
99
+ #### Authentication & Users
100
+ Automatic browser-based sign-in with Firebase. Tokens refresh transparently.
101
+
102
+ | Tool | Description |
103
+ |------|-------------|
104
+ | `create_or_login_user` | Create account or sign in |
105
+ | `get_current_user` | Your profile, admin status, org membership |
106
+ | `get_my_memberships` | List your orgs and pending invitations |
107
+ | `select_organization` | Switch org context (sets rate limits based on billing plan) |
108
+ | `get_invite_credits` | Check remaining platform invite credits |
109
+ | `send_platform_invite` | Invite someone to join SelfHost |
110
+ | `list_my_platform_invites` | See invitations you've sent |
111
+ | `reauthenticate` | Sign out and sign in again (switch accounts) |
112
+
113
+ #### Organizations & Team Management
114
+ Multi-tenant — everything is scoped to your org.
115
+
116
+ | Tool | Description |
117
+ |------|-------------|
118
+ | `list_organizations` | All orgs you belong to |
119
+ | `create_organization` | Create a new org (you become owner, get 5 invite credits) |
120
+ | `get_organization` | Org details and billing plan |
121
+ | `update_organization` | Change name, description, or IAM role |
122
+ | `delete_organization` | Soft-delete org and all related resources |
123
+ | `list_members` | See team members and their roles |
124
+ | `remove_member` | Remove a member (owner/admin only) |
125
+ | `invite_to_org` | Send org invitation with role assignment |
126
+ | `list_org_invitations` | Pending invitations |
127
+ | `cancel_invitation` | Revoke a pending invitation |
128
+ | `get_my_invitations` | Invitations sent to you |
129
+ | `list_activity_logs` | Audit trail of all actions in the org |
130
+
131
+ #### Cloud Credentials
132
+ Bring your own AWS account (BYOC) or use SelfHost's managed infrastructure.
133
+
134
+ | Tool | Description |
135
+ |------|-------------|
136
+ | `list_credentials` | List cloud credentials (secrets never exposed) |
137
+ | `add_credential` | Add AWS access keys or IAM role |
138
+ | `update_credential` | Rotate keys or update metadata |
139
+ | `delete_credential` | Remove a credential (with confirmation) |
140
+ | `set_default_credential` | Set as default for the org |
141
+ | `get_aws_account_info` | AWS account ID and free tier status |
142
+
143
+ #### Networking
144
+ VPCs, subnets, and security groups. Optional — auto-created during provisioning if not specified.
145
+
146
+ | Tool | Description |
147
+ |------|-------------|
148
+ | `list_vpcs` | List VPCs in a region (grouped by public/private) |
149
+ | `create_vpc` | Create VPC with internet gateway and subnets |
150
+ | `delete_vpc` | Remove VPC and associated resources (with confirmation) |
151
+ | `list_subnets` | List subnets in a region |
152
+ | `create_subnet` | Create subnet in a VPC |
153
+ | `list_security_groups` | List security groups |
154
+ | `create_security_group` | Create SG with inbound rules |
155
+ | `update_security_group` | Replace inbound rules on a security group |
156
+
157
+ #### Database Instances
158
+ The core feature. Full lifecycle management for PostgreSQL, MySQL, and MongoDB.
159
+
160
+ | Tool | Description |
161
+ |------|-------------|
162
+ | `list_regions` | Available AWS regions |
163
+ | `get_instance_types_for_region` | EC2 types with specs and hourly pricing |
164
+ | `list_storage_types` | EBS volume types with pricing |
165
+ | `estimate_instance_cost` | Itemized monthly cost breakdown before provisioning |
166
+ | `create_instance` | Provision a new database (guided flow with cost confirmation) |
167
+ | `list_instances` | All instances grouped by master + replicas |
168
+ | `get_instance` | Detailed instance info with latest metrics |
169
+ | `update_instance` | Change config, resize, toggle multi-AZ, update CIDR ranges |
170
+ | `stop_instance` | Stop instances (with confirmation) |
171
+ | `start_instance` | Start stopped instances |
172
+ | `reboot_instance` | Reboot instances (with confirmation) |
173
+ | `delete_instance` | Delete instances with optional final snapshot (with confirmation) |
174
+ | `fork_instance` | Clone a database into a new independent instance |
175
+ | `refresh_instance_group` | Refresh cached state for a master + replica group |
176
+
177
+ #### PostgreSQL Configuration
178
+ Tune database parameters with calculated defaults per instance type.
179
+
180
+ | Tool | Description |
181
+ |------|-------------|
182
+ | `preview_pg_config` | See calculated defaults for an instance type (before creating) |
183
+ | `get_pg_config` | Current config with defaults, overrides, and effective values |
184
+ | `update_pg_config` | Apply overrides (auto-syncs to replicas, set null to revert) |
185
+
186
+ #### Backups & Snapshots
187
+ Automated backups with DLM policies and manual EBS snapshots.
188
+
189
+ | Tool | Description |
190
+ |------|-------------|
191
+ | `list_backups` | List all backups (syncs with AWS) |
192
+ | `create_backup` | Create S3 file backup or AWS backup job |
193
+ | `delete_backup` | Delete backup and AWS snapshot (with confirmation) |
194
+ | `list_backup_policies` | List DLM lifecycle policies |
195
+ | `create_backup_policy` | Create automated backup schedule |
196
+ | `update_backup_policy` | Enable/disable or modify schedule |
197
+ | `delete_backup_policy` | Remove policy (with confirmation) |
198
+ | `list_snapshots` | List EBS snapshots in a region |
199
+ | `get_snapshot` | Snapshot details |
200
+ | `delete_snapshot` | Delete EBS snapshot (with confirmation) |
201
+ | `create_snapshot` | Create EBS snapshot from instance volume |
202
+
203
+ #### Alerts & Notifications
204
+ Monitor your databases and get notified when things go wrong.
205
+
206
+ | Tool | Description |
207
+ |------|-------------|
208
+ | `list_alert_rules` | Active alert rules |
209
+ | `create_alert_rule` | Create rule (CPU, memory, disk, replication lag, etc.) |
210
+ | `get_alert_rule` | Rule details |
211
+ | `update_alert_rule` | Modify thresholds or notification channels |
212
+ | `delete_alert_rule` | Delete rule and all instances (with confirmation) |
213
+ | `list_alert_instances` | Fired/pending/resolved alerts (paginated) |
214
+ | `get_alert_instance` | Alert instance details |
215
+ | `acknowledge_alert` | Acknowledge a firing alert |
216
+ | `resolve_alert` | Manually resolve an alert |
217
+ | `list_notification_channels` | Email notification endpoints |
218
+ | `create_notification_channel` | Add email channel |
219
+ | `update_notification_channel` | Modify channel config |
220
+ | `delete_notification_channel` | Remove channel (with confirmation) |
221
+ | `test_notification_channel` | Send test notification |
222
+
223
+ ---
224
+
225
+ ## Common Workflows
226
+
227
+ ### Switch AWS Account (BYOC vs Platform-Managed)
228
+
229
+ ```
230
+ You: "I want to use my own AWS account"
231
+
232
+ Claude: Lists your credentials → you pick one (or add new keys)
233
+ → fetches VPCs in your chosen region → you pick a VPC
234
+ → proceeds with instance creation using your account
235
+ ```
236
+
237
+ ### Switch Organization
238
+
239
+ If you belong to multiple orgs:
240
+
241
+ ```
242
+ You: "Switch to my other org"
243
+
244
+ Claude: Calls get_my_memberships → shows your orgs
245
+ → you pick one → calls select_organization
246
+ → rate limits and context update to the new org
247
+ ```
248
+
249
+ ### Re-authenticate (Switch Accounts)
250
+
251
+ If you need to sign in as a different user:
252
+
253
+ ```
254
+ You: "Sign in with a different account"
255
+
256
+ Claude: Calls reauthenticate → clears stored credentials
257
+ → opens browser for new login → saves new tokens
258
+ → you're now signed in as the other user
259
+ ```
260
+
261
+ ### Restore from Backup
262
+
263
+ ```
264
+ You: "Restore my prod database from yesterday's snapshot"
265
+
266
+ Claude: Lists snapshots → you pick one
267
+ → creates a new instance with snapshot_id
268
+ → shows you the new password (save it!)
269
+ ```
270
+
271
+ ---
272
+
273
+ ## Rate Limiting
274
+
275
+ Rate limits are based on your org's billing plan and enforced per-user:
276
+
277
+ | Plan | Reads/min | Writes/min | Concurrent |
278
+ |------|-----------|------------|------------|
279
+ | Free | ~60 | ~20 | 3 |
280
+ | Starter | ~150 | ~50 | 5 |
281
+ | Pro | ~300 | ~120 | 10 |
282
+ | Enterprise | ~600 | ~300 | Unlimited |
283
+
284
+ Reference data lookups (regions, instance types, storage types) are unlimited.
285
+
286
+ ---
287
+
288
+ ## Configuration
289
+
290
+ ### Environment Variables
291
+
292
+ | Variable | Default | Description |
293
+ |----------|---------|-------------|
294
+ | `API_URL` | `https://api.selfhost.dev` | SelfHost API endpoint |
295
+ | `CONSOLE_URL` | `https://console.selfhost.dev` | SelfHost Console URL (for browser auth) |
296
+ | `TIER` | Auto-detected | Override rate limit tier (`FREE`, `STARTER`, `PRO`, `ENTERPRISE`, `ADMIN`) |
297
+ | `FIREBASE_API_KEY` | — | Skip browser auth (CI/headless) |
298
+ | `FIREBASE_REFRESH_TOKEN` | — | Skip browser auth (CI/headless) |
299
+
300
+ ### Local Development
301
+
302
+ Both `API_URL` and `CONSOLE_URL` are optional overrides — they default to the production endpoints when not set.
303
+
304
+ **1. Local Rails API + Local Console**
305
+
306
+ Using npx:
307
+ ```json
308
+ {
309
+ "mcpServers": {
310
+ "selfhost": {
311
+ "command": "npx",
312
+ "args": ["tsx", "/path/to/selfhost-mcp/src/index.ts"],
313
+ "env": {
314
+ "API_URL": "http://localhost:3000",
315
+ "CONSOLE_URL": "http://localhost:54588"
316
+ }
317
+ }
318
+ }
319
+ }
320
+ ```
321
+
322
+ Using bun:
323
+ ```json
324
+ {
325
+ "mcpServers": {
326
+ "selfhost": {
327
+ "command": "bun",
328
+ "args": ["run", "/path/to/selfhost-mcp/src/index.ts"],
329
+ "env": {
330
+ "API_URL": "http://localhost:3000",
331
+ "CONSOLE_URL": "http://localhost:54588"
332
+ }
333
+ }
334
+ }
335
+ }
336
+ ```
337
+
338
+ **2. Local Rails API + Production Console**
339
+
340
+ ```json
341
+ {
342
+ "mcpServers": {
343
+ "selfhost": {
344
+ "command": "npx",
345
+ "args": ["tsx", "/path/to/selfhost-mcp/src/index.ts"],
346
+ "env": {
347
+ "API_URL": "http://localhost:3000"
348
+ }
349
+ }
350
+ }
351
+ }
352
+ ```
353
+
354
+ **3. Production Rails API + Local Console**
355
+
356
+ ```json
357
+ {
358
+ "mcpServers": {
359
+ "selfhost": {
360
+ "command": "npx",
361
+ "args": ["tsx", "/path/to/selfhost-mcp/src/index.ts"],
362
+ "env": {
363
+ "CONSOLE_URL": "http://localhost:54588"
364
+ }
365
+ }
366
+ }
367
+ }
368
+ ```
369
+
370
+ ### Headless / CI Mode
371
+
372
+ Set `FIREBASE_API_KEY` and `FIREBASE_REFRESH_TOKEN` environment variables to skip the browser login flow. You can find these values in `~/.selfhost/credentials.json` after your first browser login.
373
+
374
+ ---
375
+
376
+ ## Security
377
+
378
+ - **Credentials stored locally** at `~/.selfhost/credentials.json` with `chmod 0600` (owner-only read/write)
379
+ - **JWT auto-refresh** — tokens refresh 5 minutes before expiry, refresh tokens are rotated
380
+ - **Sensitive fields stripped** — passwords, AWS keys, tokens, and secrets are redacted from API responses
381
+ - **Destructive operations gated** — delete, stop, and reboot require explicit `confirm: true`
382
+ - **No console.log** — all logging goes to stderr to avoid corrupting the MCP JSON-RPC stream on stdout
package/dist/auth.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ export declare function wasAuthPrompted(): Promise<boolean>;
2
+ export declare function markAuthPrompted(): Promise<void>;
3
+ /**
4
+ * Returns true if credentials are available (env vars or stored file).
5
+ * No network calls — just a fast file existence check.
6
+ */
7
+ export declare function hasCredentials(): Promise<boolean>;
8
+ /**
9
+ * Returns a valid Firebase JWT, auto-refreshing if needed.
10
+ * Triggers browser login on first call if no credentials exist.
11
+ * Concurrent callers share the same in-flight auth promise.
12
+ */
13
+ export declare function getValidToken(): Promise<string>;
14
+ /**
15
+ * Deletes stored credentials file, clears cache, then performs fresh browser login.
16
+ * Used by the `reauthenticate` tool to switch accounts or fix bad state.
17
+ */
18
+ export declare function forceReauth(): Promise<void>;
package/dist/auth.js ADDED
@@ -0,0 +1,204 @@
1
+ import { createServer } from "node:http";
2
+ import { access, readFile, writeFile, mkdir, unlink } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ import { config } from "./config.js";
5
+ const authPromptedPath = `${dirname(config.credentialsPath)}/.auth-prompted`;
6
+ export async function wasAuthPrompted() {
7
+ try {
8
+ await access(authPromptedPath);
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ export async function markAuthPrompted() {
16
+ const dir = dirname(config.credentialsPath);
17
+ await mkdir(dir, { recursive: true });
18
+ await writeFile(authPromptedPath, new Date().toISOString(), { mode: 0o600 });
19
+ }
20
+ let cachedJwt = null;
21
+ let jwtExpiresAt = 0;
22
+ async function loadStoredCredentials() {
23
+ try {
24
+ const raw = await readFile(config.credentialsPath, "utf-8");
25
+ return JSON.parse(raw);
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ async function saveCredentials(apiKey, refreshToken) {
32
+ const dir = dirname(config.credentialsPath);
33
+ await mkdir(dir, { recursive: true });
34
+ const data = {
35
+ firebaseApiKey: apiKey,
36
+ firebaseRefreshToken: refreshToken,
37
+ savedAt: new Date().toISOString(),
38
+ };
39
+ await writeFile(config.credentialsPath, JSON.stringify(data, null, 2), { mode: 0o600 });
40
+ // Clear auth-prompted marker since we now have valid credentials
41
+ try {
42
+ await unlink(authPromptedPath);
43
+ }
44
+ catch { /* ok */ }
45
+ }
46
+ async function refreshJwt(apiKey, refreshToken) {
47
+ const url = `https://securetoken.googleapis.com/v1/token?key=${apiKey}`;
48
+ const res = await fetch(url, {
49
+ method: "POST",
50
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
51
+ body: `grant_type=refresh_token&refresh_token=${encodeURIComponent(refreshToken)}`,
52
+ });
53
+ if (!res.ok) {
54
+ const body = await res.text();
55
+ throw new Error(`Firebase token refresh failed (${res.status}): ${body}`);
56
+ }
57
+ const data = (await res.json());
58
+ return {
59
+ jwt: data.id_token,
60
+ newRefreshToken: data.refresh_token,
61
+ };
62
+ }
63
+ async function browserLogin() {
64
+ const open = (await import("open")).default;
65
+ return new Promise((resolve, reject) => {
66
+ const server = createServer((req, res) => {
67
+ const url = new URL(req.url, `http://localhost`);
68
+ if (url.pathname === "/callback") {
69
+ const refreshToken = url.searchParams.get("refresh_token");
70
+ const apiKey = url.searchParams.get("api_key");
71
+ if (!refreshToken || !apiKey) {
72
+ res.writeHead(400, { "Content-Type": "text/html" });
73
+ res.end("<h1>Missing parameters</h1><p>refresh_token and api_key are required.</p>");
74
+ return;
75
+ }
76
+ res.writeHead(200, { "Content-Type": "text/html" });
77
+ res.end("<h1>Authorization complete!</h1><p>You can close this tab and return to Claude Code.</p>");
78
+ server.close();
79
+ clearTimeout(timeout);
80
+ resolve({ apiKey, refreshToken });
81
+ }
82
+ });
83
+ // Find available port
84
+ server.listen(0, "127.0.0.1", () => {
85
+ const addr = server.address();
86
+ if (!addr || typeof addr === "string") {
87
+ reject(new Error("Failed to get server address"));
88
+ return;
89
+ }
90
+ const port = addr.port;
91
+ const authUrl = `${config.consoleUrl}/mcp-auth?port=${port}`;
92
+ config.log(`Opening browser for authentication: ${authUrl}`);
93
+ open(authUrl);
94
+ });
95
+ // 2 minute timeout
96
+ const timeout = setTimeout(() => {
97
+ server.close();
98
+ reject(new Error("Browser login timed out after 2 minutes. Please try again."));
99
+ }, 120_000);
100
+ server.on("error", (err) => {
101
+ clearTimeout(timeout);
102
+ reject(err);
103
+ });
104
+ });
105
+ }
106
+ /**
107
+ * Ensures credentials exist (from .env, stored file, or browser login).
108
+ * Returns { apiKey, refreshToken }.
109
+ */
110
+ async function resolveCredentials() {
111
+ // Priority 1: .env vars (headless/CI)
112
+ if (config.firebaseApiKey && config.firebaseRefreshToken) {
113
+ return {
114
+ apiKey: config.firebaseApiKey,
115
+ refreshToken: config.firebaseRefreshToken,
116
+ };
117
+ }
118
+ // Priority 2: Stored credentials file
119
+ const stored = await loadStoredCredentials();
120
+ if (stored) {
121
+ return {
122
+ apiKey: stored.firebaseApiKey,
123
+ refreshToken: stored.firebaseRefreshToken,
124
+ };
125
+ }
126
+ // Priority 3: Browser login
127
+ const creds = await browserLogin();
128
+ await saveCredentials(creds.apiKey, creds.refreshToken);
129
+ return creds;
130
+ }
131
+ /**
132
+ * Returns true if credentials are available (env vars or stored file).
133
+ * No network calls — just a fast file existence check.
134
+ */
135
+ export async function hasCredentials() {
136
+ if (config.firebaseApiKey && config.firebaseRefreshToken)
137
+ return true;
138
+ return (await loadStoredCredentials()) !== null;
139
+ }
140
+ let authInProgress = null;
141
+ /**
142
+ * Returns a valid Firebase JWT, auto-refreshing if needed.
143
+ * Triggers browser login on first call if no credentials exist.
144
+ * Concurrent callers share the same in-flight auth promise.
145
+ */
146
+ export async function getValidToken() {
147
+ if (authInProgress)
148
+ return authInProgress;
149
+ authInProgress = _getValidTokenImpl();
150
+ try {
151
+ return await authInProgress;
152
+ }
153
+ finally {
154
+ authInProgress = null;
155
+ }
156
+ }
157
+ async function _getValidTokenImpl() {
158
+ // Return cached JWT if still valid (5 min buffer)
159
+ const now = Date.now();
160
+ if (cachedJwt && jwtExpiresAt - now > 5 * 60 * 1000) {
161
+ return cachedJwt;
162
+ }
163
+ const creds = await resolveCredentials();
164
+ try {
165
+ const { jwt, newRefreshToken } = await refreshJwt(creds.apiKey, creds.refreshToken);
166
+ cachedJwt = jwt;
167
+ jwtExpiresAt = now + 3600 * 1000;
168
+ await saveCredentials(creds.apiKey, newRefreshToken);
169
+ return jwt;
170
+ }
171
+ catch {
172
+ // Refresh token expired — re-authenticate via browser
173
+ cachedJwt = null;
174
+ jwtExpiresAt = 0;
175
+ const newCreds = await browserLogin();
176
+ await saveCredentials(newCreds.apiKey, newCreds.refreshToken);
177
+ const { jwt, newRefreshToken } = await refreshJwt(newCreds.apiKey, newCreds.refreshToken);
178
+ cachedJwt = jwt;
179
+ jwtExpiresAt = Date.now() + 3600 * 1000;
180
+ await saveCredentials(newCreds.apiKey, newRefreshToken);
181
+ return jwt;
182
+ }
183
+ }
184
+ /**
185
+ * Deletes stored credentials file, clears cache, then performs fresh browser login.
186
+ * Used by the `reauthenticate` tool to switch accounts or fix bad state.
187
+ */
188
+ export async function forceReauth() {
189
+ cachedJwt = null;
190
+ jwtExpiresAt = 0;
191
+ try {
192
+ await unlink(config.credentialsPath);
193
+ }
194
+ catch {
195
+ // File may not exist — that's fine
196
+ }
197
+ const creds = await browserLogin();
198
+ await saveCredentials(creds.apiKey, creds.refreshToken);
199
+ const { jwt, newRefreshToken } = await refreshJwt(creds.apiKey, creds.refreshToken);
200
+ cachedJwt = jwt;
201
+ jwtExpiresAt = Date.now() + 3600 * 1000;
202
+ await saveCredentials(creds.apiKey, newRefreshToken);
203
+ }
204
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { ClientResult, HttpMethod } from "./types/api.js";
2
+ interface RequestOptions {
3
+ method?: HttpMethod;
4
+ body?: Record<string, unknown>;
5
+ query?: Record<string, string | undefined>;
6
+ toolName?: string;
7
+ skipOrgInjection?: boolean;
8
+ }
9
+ export declare function apiRequest<T>(path: string, options?: RequestOptions): Promise<ClientResult<T>>;
10
+ export {};