@selfhost.dev/mcp-server 0.1.0 → 0.2.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/README.md CHANGED
@@ -4,8 +4,6 @@ A full-featured [Model Context Protocol](https://modelcontextprotocol.io/) serve
4
4
 
5
5
  Works with **Claude Code**, **Cursor**, **Windsurf**, **Cline**, and any MCP-compatible client.
6
6
 
7
- > **JIRA Epic:** [SELFHOST-110](https://selfhost.atlassian.net/browse/SELFHOST-110)
8
-
9
7
  ---
10
8
 
11
9
  ## Quick Start
@@ -83,6 +81,12 @@ Claude: Authenticates → fetches your orgs → selects org → lists instances
83
81
  | *"Fork my production database for testing"* | Creates an independent clone with a new password |
84
82
  | *"What's my estimated monthly cost for an r6i.xlarge with 500GB gp3?"* | Calculates itemized cost breakdown |
85
83
  | *"Tune shared_buffers on my instance"* | Previews defaults, applies the override, syncs to replicas |
84
+ | *"Enable PgBouncer on my prod DB with 200 max clients"* | Configures connection pooling, clients connect on port 6432 |
85
+ | *"Add a read-only database user for the reporting app"* | Creates a DBMS-level credential with the right grants |
86
+ | *"Restore my prod database to 14:30 yesterday into a new instance"* | Sets up a point-in-time recovery from WAL + base backups |
87
+ | *"Scale my instance to r6i.2xlarge and 1TB"* | Issues a manual scale, polls the resize event |
88
+ | *"Auto-stop my staging DB at 7pm weekdays"* | Creates a scheduled scaling policy with start/stop times |
89
+ | *"Upgrade my org to the Pro plan"* | Confirms billing state, creates or changes the subscription |
86
90
 
87
91
  ### Safety Built In
88
92
 
@@ -92,7 +96,7 @@ Destructive operations (delete, stop, reboot) require explicit confirmation. The
92
96
 
93
97
  ## Features
94
98
 
95
- ### 76 tools across 8 modules
99
+ ### 104 tools across 13 modules
96
100
 
97
101
  Every tool maps to a real API endpoint. No mock data, no shortcuts.
98
102
 
@@ -220,6 +224,63 @@ Monitor your databases and get notified when things go wrong.
220
224
  | `delete_notification_channel` | Remove channel (with confirmation) |
221
225
  | `test_notification_channel` | Send test notification |
222
226
 
227
+ #### Connection Pooling (PgBouncer)
228
+ PostgreSQL-only. Reduces connection overhead for high-concurrency workloads. Clients connect on port 6432 once enabled.
229
+
230
+ | Tool | Description |
231
+ |------|-------------|
232
+ | `get_pgbouncer` | Get current pooling config (or null if never configured) |
233
+ | `enable_pgbouncer` | Enable pooling with pool mode, max client conn, pool sizes |
234
+ | `reconfigure_pgbouncer` | Apply new settings on an enabled instance (PATCH-style) |
235
+ | `disable_pgbouncer` | Drain pool and disable (with confirmation) |
236
+
237
+ #### Database Users
238
+ DBMS-level credentials — distinct from the SelfHost platform login. Use for apps and people that need their own DB user.
239
+
240
+ | Tool | Description |
241
+ |------|-------------|
242
+ | `list_database_users` | List users with role, status, target DB, expiry metadata |
243
+ | `create_database_user` | Create user with role (read_only / read_write / admin) and optional connection limit + password expiry |
244
+ | `update_database_user` | Change role, connection limit, or password expiry |
245
+ | `delete_database_user` | Soft-delete user (with confirmation) — drops active connections |
246
+ | `rotate_database_user_password` | Rotate password (with confirmation) — new value shown once |
247
+
248
+ #### Scaling
249
+ Manual one-off resize plus auto-scaling policies (reactive metric-based, scheduled cron, and auto start/stop).
250
+
251
+ | Tool | Description |
252
+ |------|-------------|
253
+ | `scale_instance` | Manual resize — change instance type, volume type, size, IOPS, throughput |
254
+ | `list_scaling_policies` | List policies, optionally filtered to one instance |
255
+ | `create_scaling_policy` | Create policy — reactive metric breach, scheduled cron, or auto start/stop schedule |
256
+ | `update_scaling_policy` | Modify thresholds, schedule, or action details |
257
+ | `delete_scaling_policy` | Remove a policy (with confirmation) |
258
+
259
+ #### Point-in-Time Recovery (PITR)
260
+ PostgreSQL-only, Pro+. Continuous WAL archiving to S3 + restore to any second within the retention window. Restore creates a brand-new instance — the source is untouched.
261
+
262
+ | Tool | Description |
263
+ |------|-------------|
264
+ | `get_pitr` | Current config + restore window (oldest / latest restorable moments) |
265
+ | `enable_pitr` | Set up WAL archiving to an S3 bucket with retention days |
266
+ | `configure_pitr` | Toggle between `enabled` (resume) and `disabled` (pause) |
267
+ | `retry_pitr` | Retry after an `error` state (e.g. fix IAM, then retry) |
268
+ | `trigger_pitr_base_backup` | Run a one-off base backup now |
269
+ | `list_pitr_base_backups` | List historical base backups with status + timestamps |
270
+ | `restore_pitr` | Restore to a target time into a new instance (with confirmation) |
271
+
272
+ #### Billing & Subscriptions
273
+ Subscription lifecycle and payment-method linking. All endpoints scoped to the active org.
274
+
275
+ | Tool | Description |
276
+ |------|-------------|
277
+ | `get_subscription` | Get current subscription (404 = no subscription, on Free) |
278
+ | `create_subscription` | Start a paid subscription (plan goes `pending` → `active` via Razorpay webhook) |
279
+ | `change_subscription` | Upgrade or downgrade plan (downgrades scheduled at cycle end) |
280
+ | `cancel_subscription` | Cancel — keeps access until cycle end (with confirmation) |
281
+ | `link_billing_payment_method` | Initiate Razorpay link — returns short URL to open in browser |
282
+ | `update_billing_contact` | Update billing phone / tax ID / country / state for invoices |
283
+
223
284
  ---
224
285
 
225
286
  ## Common Workflows
package/dist/index.js CHANGED
@@ -12,6 +12,11 @@ import { registerInstanceTools } from "./tools/instances.js";
12
12
  import { registerPgConfigTools } from "./tools/pg-config.js";
13
13
  import { registerBackupTools } from "./tools/backups.js";
14
14
  import { registerAlertTools } from "./tools/alerts.js";
15
+ import { registerPgBouncerTools } from "./tools/pgbouncer.js";
16
+ import { registerDatabaseUserTools } from "./tools/database-users.js";
17
+ import { registerScalingTools } from "./tools/scaling.js";
18
+ import { registerPitrTools } from "./tools/pitr.js";
19
+ import { registerBillingTools } from "./tools/billing.js";
15
20
  const server = new McpServer({
16
21
  name: "selfhost",
17
22
  version: "0.1.0",
@@ -32,6 +37,16 @@ registerPgConfigTools(server);
32
37
  registerBackupTools(server);
33
38
  // Module 8: Alerts & Notifications
34
39
  registerAlertTools(server);
40
+ // Module 9: PgBouncer connection pooling (Postgres, paid)
41
+ registerPgBouncerTools(server);
42
+ // Module 10: Database users (DBMS-level credentials)
43
+ registerDatabaseUserTools(server);
44
+ // Module 11: Scaling — manual + auto-scaling policies + auto start/stop
45
+ registerScalingTools(server);
46
+ // Module 12: Point-in-Time Recovery (Postgres, Pro+)
47
+ registerPitrTools(server);
48
+ // Module 13: Billing — subscriptions + payment-method linking
49
+ registerBillingTools(server);
35
50
  // Server utility tools
36
51
  server.tool("reauthenticate", "Clear stored credentials and sign in again (use to switch accounts or fix auth issues)", {}, async () => {
37
52
  try {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerBillingTools(server: McpServer): void;
@@ -0,0 +1,248 @@
1
+ import { z } from "zod";
2
+ import { apiRequest } from "../client.js";
3
+ import { session } from "../session.js";
4
+ /**
5
+ * Billing — subscription lifecycle + payment method linking.
6
+ *
7
+ * Endpoints:
8
+ * - GET /organizations/:pid/billing/subscription — current subscription
9
+ * - POST /organizations/:pid/billing/subscriptions — create (first time)
10
+ * - PATCH /organizations/:pid/billing/subscriptions — change plan (up/downgrade)
11
+ * - DELETE /organizations/:pid/billing/subscriptions — cancel
12
+ * - POST /organizations/:pid/billing/link — initiate Razorpay link
13
+ * - PATCH /organizations/:pid/billing/contact — update contact / tax info
14
+ *
15
+ * All endpoints are org-scoped via the path. The org PID is taken from the
16
+ * MCP session by default; pass `organization_id` to target a different org.
17
+ *
18
+ * Note: actually entering card details happens in a browser (Razorpay
19
+ * checkout). `link_billing_payment_method` returns the short URL the user
20
+ * needs to open; it does not complete the payment flow from here.
21
+ */
22
+ const PLANS = ["free", "starter", "pro", "enterprise"];
23
+ function resolveOrgId(organization_id) {
24
+ return organization_id || session.getActiveOrgId();
25
+ }
26
+ const noOrgMessage = "No organization selected. Call `select_organization` first or pass an organization_id.";
27
+ export function registerBillingTools(server) {
28
+ server.tool("get_subscription", "Get the current billing subscription for an organization. Returns 404 (success=false, statusCode=404) for orgs that have never subscribed (i.e. on Free from day one). Response includes billing_plan, lifecycle_state (pending | active | past_due | cancelling | cancelled | paused | revoked), next_billing_date, and Razorpay metadata.", {
29
+ organization_id: z.string().optional()
30
+ .describe("Org PID (defaults to active org)"),
31
+ }, async ({ organization_id }) => {
32
+ const orgId = resolveOrgId(organization_id);
33
+ if (!orgId) {
34
+ return { content: [{ type: "text", text: noOrgMessage }] };
35
+ }
36
+ const result = await apiRequest(`/organizations/${orgId}/billing/subscription`, {
37
+ toolName: "get_subscription",
38
+ skipOrgInjection: true,
39
+ });
40
+ if (!result.success) {
41
+ return {
42
+ content: [{
43
+ type: "text",
44
+ text: `Failed to get subscription (${result.statusCode}): ${result.message}`,
45
+ }],
46
+ };
47
+ }
48
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
49
+ });
50
+ server.tool("create_subscription", "Start a paid subscription on an organization that doesn't have one yet. BE optimistically flips org.billing_plan + lifecycle_state=`pending`, then awaits the Razorpay `subscription.activated` webhook to move it to `active`. If the org isn't already linked (no payment method), call `link_billing_payment_method` first.", {
51
+ organization_id: z.string().optional(),
52
+ billing_plan: z.enum(PLANS).describe("Target plan (starter | pro | enterprise)"),
53
+ }, async ({ organization_id, billing_plan }) => {
54
+ if (billing_plan === "free") {
55
+ return {
56
+ content: [{
57
+ type: "text",
58
+ text: "Free is the default — no subscription needed. Use `change_subscription` with billing_plan=free to downgrade an existing paid subscription.",
59
+ }],
60
+ };
61
+ }
62
+ const orgId = resolveOrgId(organization_id);
63
+ if (!orgId) {
64
+ return { content: [{ type: "text", text: noOrgMessage }] };
65
+ }
66
+ const result = await apiRequest(`/organizations/${orgId}/billing/subscriptions`, {
67
+ method: "POST",
68
+ body: { organization: { billing_plan } },
69
+ toolName: "create_subscription",
70
+ skipOrgInjection: true,
71
+ });
72
+ if (!result.success) {
73
+ return {
74
+ content: [{
75
+ type: "text",
76
+ text: `Failed to create subscription (${result.statusCode}): ${result.message}`,
77
+ }],
78
+ };
79
+ }
80
+ return {
81
+ content: [
82
+ { type: "text", text: `Subscription create requested for plan \`${billing_plan}\`. The lifecycle state will move from \`pending\` to \`active\` once Razorpay confirms — poll \`get_subscription\`.` },
83
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
84
+ ],
85
+ };
86
+ });
87
+ server.tool("change_subscription", "Change an existing subscription's plan (upgrade or downgrade). Upgrades apply immediately and prorate; downgrades are scheduled at the end of the current billing cycle. A downgrade may return 422 DOWNGRADE_BLOCKED if the org has resources above the target plan's caps (e.g. >3 instances on Starter) — surface the BE error message to the user.", {
88
+ organization_id: z.string().optional(),
89
+ billing_plan: z.enum(PLANS).describe("Target plan"),
90
+ }, async ({ organization_id, billing_plan }) => {
91
+ const orgId = resolveOrgId(organization_id);
92
+ if (!orgId) {
93
+ return { content: [{ type: "text", text: noOrgMessage }] };
94
+ }
95
+ const result = await apiRequest(`/organizations/${orgId}/billing/subscriptions`, {
96
+ method: "PATCH",
97
+ body: { organization: { billing_plan } },
98
+ toolName: "change_subscription",
99
+ skipOrgInjection: true,
100
+ });
101
+ if (!result.success) {
102
+ return {
103
+ content: [{
104
+ type: "text",
105
+ text: `Failed to change subscription (${result.statusCode}): ${result.message}`,
106
+ }],
107
+ };
108
+ }
109
+ return {
110
+ content: [
111
+ { type: "text", text: `Subscription change requested to \`${billing_plan}\`. Upgrades apply immediately; downgrades are scheduled at cycle end.` },
112
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
113
+ ],
114
+ };
115
+ });
116
+ server.tool("cancel_subscription", "Cancel the active subscription. BE moves the org to `cancelling` for the rest of the current billing cycle, then `cancelled` (which drops to Free with reduced caps). Resources above Free caps remain reachable but new ones cannot be created.", {
117
+ organization_id: z.string().optional(),
118
+ confirm: z.boolean().describe("Must be true to proceed. Cancellation takes effect at the end of the current billing cycle."),
119
+ }, async ({ organization_id, confirm }) => {
120
+ if (!confirm) {
121
+ return {
122
+ content: [{
123
+ type: "text",
124
+ text: `⚠️ About to CANCEL the subscription on the active org. You'll keep access until the end of the current billing cycle, then drop to Free.\n\nCall again with confirm=true to proceed.`,
125
+ }],
126
+ };
127
+ }
128
+ const orgId = resolveOrgId(organization_id);
129
+ if (!orgId) {
130
+ return { content: [{ type: "text", text: noOrgMessage }] };
131
+ }
132
+ const result = await apiRequest(`/organizations/${orgId}/billing/subscriptions`, {
133
+ method: "DELETE",
134
+ toolName: "cancel_subscription",
135
+ skipOrgInjection: true,
136
+ });
137
+ if (!result.success) {
138
+ return {
139
+ content: [{
140
+ type: "text",
141
+ text: `Failed to cancel subscription (${result.statusCode}): ${result.message}`,
142
+ }],
143
+ };
144
+ }
145
+ return {
146
+ content: [
147
+ { type: "text", text: "Subscription cancellation scheduled — org will move to `cancelling` until cycle end, then `cancelled`." },
148
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
149
+ ],
150
+ };
151
+ });
152
+ server.tool("link_billing_payment_method", "Initiate Razorpay payment-method linking. Returns a `short_url` the user must open in a browser to add a card / UPI. Until they do, paid features stay locked. `billing_contact_phone` is required; tax_id / country_code / state_code are optional and only used for invoices.", {
153
+ organization_id: z.string().optional(),
154
+ billing_contact_phone: z.string().min(1)
155
+ .describe("Required by Razorpay (E.164 preferred, e.g. +14155551234)"),
156
+ billing_email: z.string().email().optional()
157
+ .describe("Defaults to the current user's email on the BE if omitted"),
158
+ tax_id: z.string().optional()
159
+ .describe("GSTIN / VAT / equivalent for invoices"),
160
+ country_code: z.string().optional()
161
+ .describe("ISO 3166-1 alpha-2, e.g. IN, US, GB"),
162
+ state_code: z.string().optional()
163
+ .describe("ISO 3166-2 region code, e.g. KA for Karnataka"),
164
+ }, async ({ organization_id, billing_contact_phone, billing_email, tax_id, country_code, state_code }) => {
165
+ const orgId = resolveOrgId(organization_id);
166
+ if (!orgId) {
167
+ return { content: [{ type: "text", text: noOrgMessage }] };
168
+ }
169
+ const orgFields = {
170
+ billing_contact_phone,
171
+ };
172
+ if (billing_email)
173
+ orgFields.billing_email = billing_email;
174
+ if (tax_id)
175
+ orgFields.tax_id = tax_id;
176
+ if (country_code)
177
+ orgFields.country_code = country_code;
178
+ if (state_code)
179
+ orgFields.state_code = state_code;
180
+ const result = await apiRequest(`/organizations/${orgId}/billing/link`, {
181
+ method: "POST",
182
+ body: { organization: orgFields },
183
+ toolName: "link_billing_payment_method",
184
+ skipOrgInjection: true,
185
+ });
186
+ if (!result.success) {
187
+ return {
188
+ content: [{
189
+ type: "text",
190
+ text: `Failed to start billing link (${result.statusCode}): ${result.message}`,
191
+ }],
192
+ };
193
+ }
194
+ return {
195
+ content: [
196
+ { type: "text", text: "Razorpay link initiated. Open the `short_url` in a browser to add a card / UPI. The MCP can't complete the checkout — it's a browser-only flow." },
197
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
198
+ ],
199
+ };
200
+ });
201
+ server.tool("update_billing_contact", "Update billing contact info on an already-linked org. Doesn't re-run Razorpay — only persists DB overrides for invoices.", {
202
+ organization_id: z.string().optional(),
203
+ billing_contact_phone: z.string().optional(),
204
+ tax_id: z.string().optional()
205
+ .describe("Pass empty string to clear"),
206
+ country_code: z.string().optional(),
207
+ state_code: z.string().optional(),
208
+ }, async ({ organization_id, billing_contact_phone, tax_id, country_code, state_code }) => {
209
+ const orgId = resolveOrgId(organization_id);
210
+ if (!orgId) {
211
+ return { content: [{ type: "text", text: noOrgMessage }] };
212
+ }
213
+ const orgFields = {};
214
+ if (billing_contact_phone !== undefined && billing_contact_phone.length > 0) {
215
+ orgFields.billing_contact_phone = billing_contact_phone;
216
+ }
217
+ if (tax_id !== undefined)
218
+ orgFields.tax_id = tax_id;
219
+ if (country_code !== undefined)
220
+ orgFields.country_code = country_code;
221
+ if (state_code !== undefined)
222
+ orgFields.state_code = state_code;
223
+ if (Object.keys(orgFields).length === 0) {
224
+ return {
225
+ content: [{
226
+ type: "text",
227
+ text: "No changes to apply — pass at least one of billing_contact_phone / tax_id / country_code / state_code.",
228
+ }],
229
+ };
230
+ }
231
+ const result = await apiRequest(`/organizations/${orgId}/billing/contact`, {
232
+ method: "PATCH",
233
+ body: { organization: orgFields },
234
+ toolName: "update_billing_contact",
235
+ skipOrgInjection: true,
236
+ });
237
+ if (!result.success) {
238
+ return {
239
+ content: [{
240
+ type: "text",
241
+ text: `Failed to update billing contact (${result.statusCode}): ${result.message}`,
242
+ }],
243
+ };
244
+ }
245
+ return { content: [{ type: "text", text: "Billing contact updated." }] };
246
+ });
247
+ }
248
+ //# sourceMappingURL=billing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"billing.js","sourceRoot":"","sources":["../../src/tools/billing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerDatabaseUserTools(server: McpServer): void;
@@ -0,0 +1,181 @@
1
+ import { z } from "zod";
2
+ import { apiRequest } from "../client.js";
3
+ /**
4
+ * Database users — DBMS-level credentials (distinct from the platform login).
5
+ *
6
+ * Endpoints:
7
+ * - GET /aws/v1/instances/:id/database_users
8
+ * - POST /aws/v1/instances/:id/database_users
9
+ * - PATCH /aws/v1/instances/:id/database_users/:user_id
10
+ * - DELETE /aws/v1/instances/:id/database_users/:user_id
11
+ * - POST /aws/v1/instances/:id/database_users/:user_id/rotate_password
12
+ *
13
+ * Use these to provision separate credentials for apps / people instead of
14
+ * sharing the master admin password.
15
+ */
16
+ const ROLES = ["read_only", "read_write", "admin"];
17
+ export function registerDatabaseUserTools(server) {
18
+ server.tool("list_database_users", "List DBMS-level users on an instance. Returns each user's id, username, role, status (active | pending | deleting), target database, connection limit, and password expiry metadata.", {
19
+ instance_id: z.string().min(1),
20
+ }, async ({ instance_id }) => {
21
+ const result = await apiRequest(`/aws/v1/instances/${instance_id}/database_users`, {
22
+ toolName: "list_database_users",
23
+ skipOrgInjection: true,
24
+ });
25
+ if (!result.success) {
26
+ return {
27
+ content: [{
28
+ type: "text",
29
+ text: `Failed to list database users (${result.statusCode}): ${result.message}`,
30
+ }],
31
+ };
32
+ }
33
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
34
+ });
35
+ server.tool("create_database_user", "Create a DBMS-level user on an instance. Username 1-31 chars, letters/digits/underscores only. Password 8-128 printable chars. Role determines DB privileges: read_only (SELECT), read_write (SELECT/INSERT/UPDATE/DELETE), admin (broader DDL). Target database scopes the grant to one DB; leave empty for cluster-level access.", {
36
+ instance_id: z.string().min(1),
37
+ username: z.string().min(1).max(31).regex(/^[a-zA-Z0-9_]+$/, "letters, digits, underscores only"),
38
+ password: z.string().min(8).max(128),
39
+ role: z.enum(ROLES),
40
+ target_database: z.string().optional().describe("Specific DB to grant on; omit for cluster-wide"),
41
+ connection_limit: z.number().int().min(1).max(1000).optional()
42
+ .describe("Max concurrent connections for this user (default unlimited)"),
43
+ password_expiry_days: z.number().int().min(1).max(365).optional()
44
+ .describe("Days until the password expires; omit for no expiry"),
45
+ }, async ({ instance_id, username, password, role, target_database, connection_limit, password_expiry_days }) => {
46
+ const body = {
47
+ username,
48
+ password,
49
+ role,
50
+ };
51
+ if (target_database !== undefined)
52
+ body.target_database = target_database;
53
+ if (connection_limit !== undefined)
54
+ body.connection_limit = connection_limit;
55
+ if (password_expiry_days !== undefined)
56
+ body.password_expiry_days = password_expiry_days;
57
+ const result = await apiRequest(`/aws/v1/instances/${instance_id}/database_users`, {
58
+ method: "POST",
59
+ body,
60
+ toolName: "create_database_user",
61
+ skipOrgInjection: true,
62
+ });
63
+ if (!result.success) {
64
+ return {
65
+ content: [{
66
+ type: "text",
67
+ text: `Failed to create database user (${result.statusCode}): ${result.message}`,
68
+ }],
69
+ };
70
+ }
71
+ return {
72
+ content: [
73
+ { type: "text", text: `Database user \`${username}\` create requested. Status will transition from \`pending\` to \`active\` once applied — poll \`list_database_users\`.` },
74
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
75
+ ],
76
+ };
77
+ });
78
+ server.tool("update_database_user", "Update a database user — change role, connection limit, or password expiry. Username and target database are immutable (delete + recreate to change them).", {
79
+ instance_id: z.string().min(1),
80
+ user_id: z.string().min(1).describe("Database user PID (from list_database_users)"),
81
+ role: z.enum(ROLES).optional(),
82
+ connection_limit: z.number().int().min(1).max(1000).optional(),
83
+ password_expiry_days: z.number().int().min(0).max(365).optional()
84
+ .describe("0 removes expiry; otherwise sets a new countdown from now"),
85
+ }, async ({ instance_id, user_id, role, connection_limit, password_expiry_days }) => {
86
+ const body = {};
87
+ if (role !== undefined)
88
+ body.role = role;
89
+ if (connection_limit !== undefined)
90
+ body.connection_limit = connection_limit;
91
+ if (password_expiry_days !== undefined)
92
+ body.password_expiry_days = password_expiry_days;
93
+ if (Object.keys(body).length === 0) {
94
+ return {
95
+ content: [{
96
+ type: "text",
97
+ text: "No changes to apply — pass at least one of role / connection_limit / password_expiry_days.",
98
+ }],
99
+ };
100
+ }
101
+ const result = await apiRequest(`/aws/v1/instances/${instance_id}/database_users/${user_id}`, {
102
+ method: "PATCH",
103
+ body,
104
+ toolName: "update_database_user",
105
+ skipOrgInjection: true,
106
+ });
107
+ if (!result.success) {
108
+ return {
109
+ content: [{
110
+ type: "text",
111
+ text: `Failed to update database user (${result.statusCode}): ${result.message}`,
112
+ }],
113
+ };
114
+ }
115
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
116
+ });
117
+ server.tool("delete_database_user", "Soft-delete a database user. Existing connections opened by this user are terminated. The record is preserved for audit — contact support to recover.", {
118
+ instance_id: z.string().min(1),
119
+ user_id: z.string().min(1),
120
+ username: z.string().optional().describe("Username for the confirmation message (cosmetic)"),
121
+ confirm: z.boolean().describe("Must be true to proceed. Existing connections will be dropped."),
122
+ }, async ({ instance_id, user_id, username, confirm }) => {
123
+ if (!confirm) {
124
+ const label = username ? `\`${username}\` (${user_id})` : user_id;
125
+ return {
126
+ content: [{
127
+ type: "text",
128
+ text: `⚠️ About to DELETE database user ${label} on instance ${instance_id}. Apps/scripts using this credential will lose access immediately.\n\nCall again with confirm=true to proceed.`,
129
+ }],
130
+ };
131
+ }
132
+ const result = await apiRequest(`/aws/v1/instances/${instance_id}/database_users/${user_id}`, {
133
+ method: "DELETE",
134
+ toolName: "delete_database_user",
135
+ skipOrgInjection: true,
136
+ });
137
+ if (!result.success) {
138
+ return {
139
+ content: [{
140
+ type: "text",
141
+ text: `Failed to delete database user (${result.statusCode}): ${result.message}`,
142
+ }],
143
+ };
144
+ }
145
+ return { content: [{ type: "text", text: "Database user delete requested — status will move to `deleting` and then disappear from the list." }] };
146
+ });
147
+ server.tool("rotate_database_user_password", "Rotate the password for a database user. The new password is generated server-side and returned ONCE in the response. SECURITY: save it immediately — there is no way to retrieve it later. Existing connections using the old password are terminated.", {
148
+ instance_id: z.string().min(1),
149
+ user_id: z.string().min(1),
150
+ confirm: z.boolean().describe("Must be true. Old password is invalidated immediately; apps and scripts must pick up the new value."),
151
+ }, async ({ instance_id, user_id, confirm }) => {
152
+ if (!confirm) {
153
+ return {
154
+ content: [{
155
+ type: "text",
156
+ text: `⚠️ About to ROTATE the password for database user ${user_id} on instance ${instance_id}.\n\nThe new password is shown ONCE in the response — save it before doing anything else.\nApps and scripts using the old password will lose connections immediately.\n\nCall again with confirm=true to proceed.`,
157
+ }],
158
+ };
159
+ }
160
+ const result = await apiRequest(`/aws/v1/instances/${instance_id}/database_users/${user_id}/rotate_password`, {
161
+ method: "POST",
162
+ toolName: "rotate_database_user_password",
163
+ skipOrgInjection: true,
164
+ });
165
+ if (!result.success) {
166
+ return {
167
+ content: [{
168
+ type: "text",
169
+ text: `Failed to rotate password (${result.statusCode}): ${result.message}`,
170
+ }],
171
+ };
172
+ }
173
+ return {
174
+ content: [
175
+ { type: "text", text: "🔑 New password generated. Copy it now — it will not be shown again." },
176
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
177
+ ],
178
+ };
179
+ });
180
+ }
181
+ //# sourceMappingURL=database-users.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-users.js","sourceRoot":"","sources":["../../src/tools/database-users.ts"],"names":[],"mappings":"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"}