@selfhost.dev/mcp-server 0.6.0 → 0.8.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.
@@ -7,6 +7,7 @@ import { session } from "../session.js";
7
7
  *
8
8
  * Endpoints (base /api/v1/platform/github_repo_deployments/:pid/env_vars):
9
9
  * - GET /env_vars list { key, value }
10
+ * - GET /env_vars/detect scan the repo for the vars it expects
10
11
  * - PUT /env_vars FULL REPLACE - deletes keys not in the payload
11
12
  * - PATCH /env_vars merge/upsert - never deletes
12
13
  * - DELETE /env_vars/:key remove one key
@@ -49,6 +50,28 @@ export function registerEnvVarTools(server) {
49
50
  }
50
51
  return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
51
52
  });
53
+ server.tool("detect_env_vars", `Scan a deployment's repo/branch for the environment variables it expects, and report which are already configured. Read-only - it changes nothing.
54
+
55
+ Returns \`detected_vars\` [{ key, hint, source, configured, configured_value }] and a \`sources\` summary (.env.example, GitHub workflow secrets, CI files, Dockerfile/compose, framework config). \`hint\` is the placeholder found in the repo - a hint, NOT a real secret, so never set it as a value; ask the user for each missing one.
56
+
57
+ Use it right after creating a deployment (or when a build fails on a missing variable) to find out what still needs setting, then apply the answers with \`merge_env_vars\`. Vars already stored come back with configured=true so you can skip them.`, {
58
+ deployment_pid: z.string().min(1),
59
+ }, async ({ deployment_pid }) => {
60
+ if (!hasOrg())
61
+ return { content: [{ type: "text", text: NO_ORG }] };
62
+ const result = await apiRequest(`${DEPLOYMENTS_BASE}/${deployment_pid}/env_vars/detect`, {
63
+ toolName: "detect_env_vars",
64
+ });
65
+ if (!result.success) {
66
+ return { content: [{ type: "text", text: `Failed to detect env vars (${result.statusCode}): ${result.message}` }] };
67
+ }
68
+ return {
69
+ content: [
70
+ { type: "text", text: "Detected from the repo. `hint` values are placeholders from the source files - ask the user for the real values, don't set the hints." },
71
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
72
+ ],
73
+ };
74
+ });
52
75
  server.tool("set_env_vars", "REPLACE a deployment's ENTIRE set of environment variables with the provided map. ⚠️ Any existing key NOT included here is DELETED. To add or edit a few variables without removing the rest, use `merge_env_vars` instead - or call `list_env_vars` first and pass the full merged set here. Keys must match ^[A-Za-z_][A-Za-z0-9_]*$; values must be non-empty. Saving may trigger a rate-limited redeploy.", {
53
76
  deployment_pid: z.string().min(1),
54
77
  env_vars: z.record(z.string()).describe("The COMPLETE desired set { KEY: value }; replaces all existing vars"),
@@ -1 +1 @@
1
- {"version":3,"file":"env-vars.js","sourceRoot":"","sources":["../../src/tools/env-vars.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"env-vars.js","sourceRoot":"","sources":["../../src/tools/env-vars.ts"],"names":[],"mappings":"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"}
@@ -6,10 +6,12 @@ import { session } from "../session.js";
6
6
  * list branches, auto-detect a repo's build config, and get the install URL.
7
7
  *
8
8
  * Endpoints:
9
- * - GET /api/v1/platform/github_installations
10
- * - GET /api/v1/platform/github_installations/:installation_pid/branches?repo_full_name=
11
- * - POST /api/v1/platform/github_installations/detect_repo_config
12
- * - GET /github/connect?organization_pid= (returns a browser install URL)
9
+ * - GET /api/v1/platform/github_installations
10
+ * - GET /api/v1/platform/github_installations/:installation_pid/branches?repo_full_name=
11
+ * - POST /api/v1/platform/github_installations/detect_repo_config
12
+ * - DELETE /api/v1/platform/github_installations/:installation_pid (one)
13
+ * - DELETE /api/v1/platform/github_installations/disconnect (all, this org)
14
+ * - GET /github/connect?organization_pid= (returns a browser install URL)
13
15
  *
14
16
  * NOTE: connecting a GitHub account is an interactive, browser-based GitHub App
15
17
  * installation - it CANNOT be completed headlessly. `get_github_connect_url`
@@ -102,5 +104,64 @@ export function registerGithubTools(server) {
102
104
  }],
103
105
  };
104
106
  });
107
+ // No `connected_accounts` tool. `GET /github_installations/connected_accounts` 500s:
108
+ // the action calls `current_user.organization_users`, an association that does not exist
109
+ // (it is `organization_memberships`), so it has never worked and the console never calls
110
+ // it. `list_github_installations` covers the useful case for the active org.
111
+ server.tool("disconnect_github_installation", "DESTRUCTIVE: disconnect one GitHub App installation from the active organization. Pushes for its repos stop triggering deploys immediately. The backend refuses while deployments are still using the installation - delete or repoint those first. This only detaches it on the SelfHost side; the response includes a GitHub URL the user must open to revoke the app on GitHub itself. Reconnecting later is a browser flow (`get_github_connect_url`).", {
112
+ installation_pid: z.string().min(1).describe("Installation pid (ghi_...), from `list_github_installations`"),
113
+ confirm: z.boolean().describe("Must be true to proceed."),
114
+ }, async ({ installation_pid, confirm }) => {
115
+ if (!orgId())
116
+ return { content: [{ type: "text", text: NO_ORG }] };
117
+ if (!confirm) {
118
+ return {
119
+ content: [{
120
+ type: "text",
121
+ text: `⚠️ About to DISCONNECT GitHub installation ${installation_pid}. Auto-deploys from its repositories stop immediately.\n\nCall again with confirm=true to proceed.`,
122
+ }],
123
+ };
124
+ }
125
+ const result = await apiRequest(`${INSTALLATIONS_BASE}/${installation_pid}`, {
126
+ method: "DELETE",
127
+ toolName: "disconnect_github_installation",
128
+ });
129
+ if (!result.success) {
130
+ return { content: [{ type: "text", text: `Failed to disconnect installation (${result.statusCode}): ${result.message}` }] };
131
+ }
132
+ return {
133
+ content: [
134
+ { type: "text", text: "Installation disconnected. Open the GitHub revoke URL below to remove the app on GitHub's side as well." },
135
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
136
+ ],
137
+ };
138
+ });
139
+ server.tool("disconnect_all_github_installations", "DESTRUCTIVE: disconnect EVERY GitHub App installation from the active organization at once. All auto-deploys stop. Blocked by the backend while any deployment still uses one of them. Returns `disconnected_count` and a `github_revoke_urls` list the user must open to revoke the app on GitHub. Prefer `disconnect_github_installation` when only one account should go.", {
140
+ confirm: z.boolean().describe("Must be true to proceed."),
141
+ }, async ({ confirm }) => {
142
+ if (!orgId())
143
+ return { content: [{ type: "text", text: NO_ORG }] };
144
+ if (!confirm) {
145
+ return {
146
+ content: [{
147
+ type: "text",
148
+ text: "⚠️ About to disconnect ALL GitHub installations for this organization. Every repository's auto-deploys stop immediately.\n\nCall again with confirm=true to proceed.",
149
+ }],
150
+ };
151
+ }
152
+ const result = await apiRequest(`${INSTALLATIONS_BASE}/disconnect`, {
153
+ method: "DELETE",
154
+ toolName: "disconnect_all_github_installations",
155
+ });
156
+ if (!result.success) {
157
+ return { content: [{ type: "text", text: `Failed to disconnect installations (${result.statusCode}): ${result.message}` }] };
158
+ }
159
+ return {
160
+ content: [
161
+ { type: "text", text: "GitHub installations disconnected. Open each URL in `github_revoke_urls` to revoke the app on GitHub's side as well." },
162
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
163
+ ],
164
+ };
165
+ });
105
166
  }
106
167
  //# sourceMappingURL=github.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/tools/github.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/tools/github.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,kBAAkB,GAAG,uCAAuC,CAAC;AAEnE,MAAM,MAAM,GACV,qFAAqF,CAAC;AAExF,SAAS,KAAK;IACZ,OAAO,OAAO,CAAC,cAAc,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,MAAM,CAAC,IAAI,CACT,2BAA2B,EAC3B,yXAAyX,EACzX,EAAE,EACF,KAAK,IAAI,EAAE;QACT,IAAI,CAAC,KAAK,EAAE;YAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QACnE,MAAM,MAAM,GAAG,MAAM,UAAU,CAAU,kBAAkB,EAAE;YAC3D,QAAQ,EAAE,2BAA2B;SACtC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,wCAAwC,MAAM,CAAC,UAAU,MAAM,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC;QAChI,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACrF,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,oBAAoB,EACpB,uSAAuS,EACvS;QACE,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,mCAAmC,CAAC;QACjF,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC;QACvF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAC;QACrE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;KAClG,EACD,KAAK,EAAE,EAAE,gBAAgB,EAAE,cAAc,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;QAC7D,IAAI,CAAC,KAAK,EAAE;YAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QACnE,MAAM,MAAM,GAAG,MAAM,UAAU,CAAU,GAAG,kBAAkB,IAAI,gBAAgB,WAAW,EAAE;YAC7F,KAAK,EAAE;gBACL,cAAc;gBACd,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;gBACnD,QAAQ,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;aAChE;YACD,QAAQ,EAAE,oBAAoB;SAC/B,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,4BAA4B,MAAM,CAAC,UAAU,MAAM,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC;QACpH,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACrF,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,oBAAoB,EACpB,8bAA8b,EAC9b;QACE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,+BAA+B,CAAC;QACrE,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,wDAAwD,CAAC;QAC1G,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,wDAAwD,CAAC;KACjG,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,EAAE,EAAE;QAC/C,IAAI,CAAC,KAAK,EAAE;YAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,GAA4B,EAAE,QAAQ,EAAE,CAAC;QACnD,IAAI,gBAAgB,KAAK,SAAS;YAAE,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;QAC7E,IAAI,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAE/C,MAAM,MAAM,GAAG,MAAM,UAAU,CAAU,GAAG,kBAAkB,qBAAqB,EAAE;YACnF,MAAM,EAAE,MAAM;YACd,IAAI;YACJ,QAAQ,EAAE,oBAAoB;SAC/B,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iCAAiC,MAAM,CAAC,UAAU,MAAM,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC;QACzH,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACrF,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,wBAAwB,EACxB,2XAA2X,EAC3X;QACE,oBAAoB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qDAAqD,CAAC;KAClH,EACD,KAAK,EAAE,EAAE,oBAAoB,EAAE,EAAE,EAAE;QACjC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;QACnB,IAAI,CAAC,EAAE;YAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAC9D,uGAAuG;QACvG,MAAM,MAAM,GAAG,MAAM,UAAU,CAA4B,iBAAiB,EAAE;YAC5E,KAAK,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,oBAAoB,EAAE;YACrD,QAAQ,EAAE,wBAAwB;YAClC,gBAAgB,EAAE,IAAI;SACvB,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,qCAAqC,MAAM,CAAC,UAAU,MAAM,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC;QAC7H,CAAC;QACD,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,IAAK,MAAM,CAAC,IAAkC,CAAC,YAAY,CAAC,IAAI,IAAI,CAAC;QAC7F,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,sDAAsD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;QAC7I,CAAC;QACD,OAAO;YACL,OAAO,EAAE,CAAC;oBACR,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,mGAAmG,GAAG,wFAAwF;iBACrM,CAAC;SACH,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,qFAAqF;IACrF,yFAAyF;IACzF,yFAAyF;IACzF,6EAA6E;IAE7E,MAAM,CAAC,IAAI,CACT,gCAAgC,EAChC,4bAA4b,EAC5b;QACE,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,8DAA8D,CAAC;QAC5G,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC;KAC1D,EACD,KAAK,EAAE,EAAE,gBAAgB,EAAE,OAAO,EAAE,EAAE,EAAE;QACtC,IAAI,CAAC,KAAK,EAAE;YAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QACnE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,+CAA+C,gBAAgB,oGAAoG;qBAC1K,CAAC;aACH,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAU,GAAG,kBAAkB,IAAI,gBAAgB,EAAE,EAAE;YACpF,MAAM,EAAE,QAAQ;YAChB,QAAQ,EAAE,gCAAgC;SAC3C,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,sCAAsC,MAAM,CAAC,UAAU,MAAM,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC;QAC9H,CAAC;QACD,OAAO;YACL,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,yGAAyG,EAAE;gBACjI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;aAC7D;SACF,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,qCAAqC,EACrC,8WAA8W,EAC9W;QACE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC;KAC1D,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACpB,IAAI,CAAC,KAAK,EAAE;YAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QACnE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,uKAAuK;qBAC9K,CAAC;aACH,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAU,GAAG,kBAAkB,aAAa,EAAE;YAC3E,MAAM,EAAE,QAAQ;YAChB,QAAQ,EAAE,qCAAqC;SAChD,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,uCAAuC,MAAM,CAAC,UAAU,MAAM,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC;QAC/H,CAAC;QACD,OAAO;YACL,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,sHAAsH,EAAE;gBAC9I,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;aAC7D;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -51,11 +51,23 @@ const ENGINE_VERSIONS = {
51
51
  clickhouse: CLICKHOUSE_VERSIONS,
52
52
  };
53
53
  /**
54
- * ClickHouse default when the caller passes no version. The backend would fall back to
55
- * the oldest supported major (24.3); the console prefers the 24.8 LTS - the same
56
- * "prefer the stable release" stance MySQL takes with 8.0 over 8.4 - so send it explicitly.
54
+ * Version sent when the caller passes none.
55
+ *
56
+ * This has to be explicit on every engine: the backend's fallback is
57
+ * `supported_versions.first`, which is the OLDEST major each adapter still accepts
58
+ * (postgres 14, redis 7.0, clickhouse 24.3), so omitting `db_version` silently
59
+ * provisions a years-old release. These mirror the console's create-wizard defaults
60
+ * (`CreateDatabaseOptions`) so both surfaces hand out the same database - newest for
61
+ * postgres/redis, and the stable release rather than the newest for mysql (8.0 over 8.4)
62
+ * and clickhouse (the 24.8 LTS over 25.1/26.5).
57
63
  */
58
- const CLICKHOUSE_DEFAULT_VERSION = "24.8";
64
+ const ENGINE_DEFAULT_VERSION = {
65
+ postgres: "18",
66
+ mysql: "8.0",
67
+ redis: "7.2",
68
+ clickhouse: "24.8",
69
+ };
70
+ const CLICKHOUSE_DEFAULT_VERSION = ENGINE_DEFAULT_VERSION.clickhouse;
59
71
  /**
60
72
  * Hard RAM floor per engine - backend `InstancesController::MIN_RAM_GB`. ClickHouse below
61
73
  * 4 GiB fails its systemd Type=notify startup, so create is rejected outright.
@@ -121,6 +133,95 @@ const tagSchema = z.object({
121
133
  key: z.string().min(1),
122
134
  value: z.string(),
123
135
  });
136
+ /**
137
+ * Collapse heartbeat frames into per-instance, per-metric stats. A day of heartbeats
138
+ * from one instance is hundreds of frames; the agent consuming this needs the shape of
139
+ * the series (is it climbing? how high did it get?), not every sample.
140
+ *
141
+ * Non-numeric metric values are skipped rather than coerced - a metric that reports a
142
+ * string would otherwise land in the summary as NaN.
143
+ */
144
+ function summarizeMetrics(frames) {
145
+ const byInstance = new Map();
146
+ const ordered = [...frames].sort((a, b) => a.timestamp_ms - b.timestamp_ms);
147
+ for (const frame of ordered) {
148
+ const key = frame.resource?.pid ?? frame.resource?.agent_id ?? "unknown";
149
+ let entry = byInstance.get(key);
150
+ if (!entry) {
151
+ entry = {
152
+ instance_pid: frame.resource?.pid ?? null,
153
+ agent_id: frame.resource?.agent_id ?? null,
154
+ host_name: frame.resource?.host_name ?? null,
155
+ samples: 0,
156
+ first_sample: null,
157
+ last_sample: null,
158
+ metric_sets: {},
159
+ };
160
+ byInstance.set(key, entry);
161
+ }
162
+ const at = new Date(frame.timestamp_ms).toISOString();
163
+ entry.first_sample ??= at;
164
+ entry.last_sample = at;
165
+ entry.samples += 1;
166
+ for (const set of frame.metric_sets ?? []) {
167
+ // Storage arrives as one set PER MOUNT POINT (/, /boot, and the engine's data
168
+ // volume), all typed "storage". Merging them would blend a 97%-full /boot into the
169
+ // data volume's numbers, so each mount keeps its own bucket - the same axis alert
170
+ // rules use, where `mount_point` is required.
171
+ const mount = set.attributes?.mount_point;
172
+ const bucketKey = typeof mount === "string" && mount.length > 0 ? `${set.type}[${mount}]` : set.type;
173
+ const bucket = (entry.metric_sets[bucketKey] ??= {});
174
+ for (const [name, value] of Object.entries(set.metrics ?? {})) {
175
+ if (typeof value !== "number" || !Number.isFinite(value))
176
+ continue;
177
+ const stat = bucket[name];
178
+ if (!stat) {
179
+ bucket[name] = { latest: value, min: value, max: value, avg: value, samples: 1 };
180
+ }
181
+ else {
182
+ // avg is kept as a running mean so the whole series never has to be held.
183
+ stat.avg = (stat.avg * stat.samples + value) / (stat.samples + 1);
184
+ stat.samples += 1;
185
+ stat.latest = value;
186
+ if (value < stat.min)
187
+ stat.min = value;
188
+ if (value > stat.max)
189
+ stat.max = value;
190
+ }
191
+ }
192
+ }
193
+ }
194
+ const round = (n) => Math.round(n * 1000) / 1000;
195
+ return [...byInstance.values()].map((entry) => ({
196
+ ...entry,
197
+ metric_sets: Object.fromEntries(Object.entries(entry.metric_sets).map(([type, metrics]) => [
198
+ type,
199
+ Object.fromEntries(Object.entries(metrics).map(([name, stat]) => [
200
+ name,
201
+ { latest: round(stat.latest), min: round(stat.min), max: round(stat.max), avg: round(stat.avg), samples: stat.samples },
202
+ ])),
203
+ ])),
204
+ }));
205
+ }
206
+ /**
207
+ * Present prices the way the console and the website do: all-in, with the platform
208
+ * margin folded into each infrastructure line instead of shown as its own row.
209
+ *
210
+ * The pricing API returns pre-margin bases plus one lump `markup_monthly`, so the lump
211
+ * is redistributed across the lines it was charged on (compute + storage; public IPv4
212
+ * is never marked up). Line sums stay exactly equal to `total_monthly`.
213
+ */
214
+ function foldMarkup(e) {
215
+ const base = e.breakdown.compute_base_monthly + e.breakdown.storage_base_monthly;
216
+ const multiplier = base > 0 ? 1 + e.breakdown.markup_monthly / base : 1;
217
+ return {
218
+ compute: e.breakdown.compute_base_monthly * multiplier,
219
+ storage: e.breakdown.storage_base_monthly * multiplier,
220
+ instanceHourly: e.unit_prices.instance_hourly * multiplier,
221
+ instanceMonthly: e.unit_prices.instance_monthly * multiplier,
222
+ storageMonthly: e.unit_prices.storage_monthly * multiplier,
223
+ };
224
+ }
124
225
  export function registerInstanceTools(server) {
125
226
  // --- Reference data tools (exempt from rate limits) ---
126
227
  server.tool("list_regions", "List available AWS regions for instance deployment", {}, async () => {
@@ -179,7 +280,10 @@ export function registerInstanceTools(server) {
179
280
  server.tool("estimate_instance_cost", `Estimate the monthly cost of a database instance BEFORE creating it using the SelfHost pricing API.
180
281
  Call this to present accurate cost to the user for approval before calling create_instance.
181
282
 
182
- Returns an itemized cost breakdown including compute, storage, markup, and add-ons.
283
+ Returns an itemized breakdown of compute, storage and add-ons. Prices are ALL-IN: the
284
+ platform margin is folded into each line rather than billed separately, matching the
285
+ console and the website - so quote these numbers to the user as-is and never add a
286
+ platform fee on top.
183
287
 
184
288
  Pass type_of_dbms whenever you know the engine. ClickHouse prices differently: with
185
289
  replica_count=1 the platform provisions a third, keeper-only node (${CLICKHOUSE_KEEPER_INSTANCE_TYPE} + ${CLICKHOUSE_KEEPER_STORAGE_GB} GB) to hold
@@ -244,24 +348,25 @@ out for a ClickHouse cluster and the estimate understates the real bill.`, {
244
348
  keeperError = `Could not price the Keeper node (${keeperResult.statusCode}): ${keeperResult.message}`;
245
349
  }
246
350
  }
351
+ const allIn = foldMarkup(data);
247
352
  const lines = [
248
- "Estimated Monthly Cost Breakdown",
353
+ "Estimated Monthly Cost (all-in)",
249
354
  "=================================",
250
355
  `Region: ${data.region}`,
251
356
  `Instance: ${data.instance_type}`,
252
- ` $${data.unit_prices.instance_hourly.toFixed(4)}/hr → $${data.unit_prices.instance_monthly.toFixed(2)}/mo`,
253
- `Storage: ${data.storage_size_gb} GiB ${data.storage_type} → $${data.unit_prices.storage_monthly.toFixed(2)}/mo`,
357
+ ` $${allIn.instanceHourly.toFixed(4)}/hr → $${allIn.instanceMonthly.toFixed(2)}/mo`,
358
+ `Storage: ${data.storage_size_gb} GiB ${data.storage_type} → $${allIn.storageMonthly.toFixed(2)}/mo`,
254
359
  `Databases: ${data.database_count}`,
255
360
  `Replicas: ${data.replica_count}`,
256
361
  "",
257
362
  "Breakdown:",
258
- ` Compute: $${data.breakdown.compute_base_monthly.toFixed(2)}`,
259
- ` Storage: $${data.breakdown.storage_base_monthly.toFixed(2)}`,
260
- ` Markup: $${data.breakdown.markup_monthly.toFixed(2)}`,
363
+ ` Compute: $${allIn.compute.toFixed(2)}`,
364
+ ` Storage: $${allIn.storage.toFixed(2)}`,
261
365
  ` Public IPv4: $${data.breakdown.public_ipv4_monthly.toFixed(2)}`,
262
366
  ];
263
367
  if (keeper) {
264
- lines.push("", `ClickHouse Keeper node (${CLICKHOUSE_KEEPER_INSTANCE_TYPE} + ${CLICKHOUSE_KEEPER_STORAGE_GB} GiB ${keeper.storage_type}):`, ` Compute: $${keeper.breakdown.compute_base_monthly.toFixed(2)}`, ` Storage: $${keeper.breakdown.storage_base_monthly.toFixed(2)}`, ` Markup: $${keeper.breakdown.markup_monthly.toFixed(2)}`, ` Subtotal: $${keeper.total_monthly.toFixed(2)}`, " (Required third quorum member because replica_count=1. Two replicas instead run the", " quorum embedded on the data nodes - no Keeper node and no third line item.)");
368
+ const keeperAllIn = foldMarkup(keeper);
369
+ lines.push("", `ClickHouse Keeper node (${CLICKHOUSE_KEEPER_INSTANCE_TYPE} + ${CLICKHOUSE_KEEPER_STORAGE_GB} GiB ${keeper.storage_type}):`, ` Compute: $${keeperAllIn.compute.toFixed(2)}`, ` Storage: $${keeperAllIn.storage.toFixed(2)}`, ` Subtotal: $${keeper.total_monthly.toFixed(2)}`, " (Required third quorum member because replica_count=1. Two replicas instead run the", " quorum embedded on the data nodes - no Keeper node and no third line item.)");
265
370
  }
266
371
  const total = data.total_monthly + (keeper?.total_monthly ?? 0);
267
372
  lines.push("", `TOTAL: $${total.toFixed(2)}/month`);
@@ -271,7 +376,7 @@ out for a ClickHouse cluster and the estimate understates the real bill.`, {
271
376
  if (keeperError) {
272
377
  lines.push("", `⚠️ ${keeperError}`, "This total EXCLUDES the Keeper node that a 1-replica ClickHouse cluster requires - the real bill will be higher.");
273
378
  }
274
- lines.push("", `Pricing model: ${data.pricing_model}`, `Currency: ${data.currency}`);
379
+ lines.push("", `Pricing model: ${data.pricing_model}`, `Currency: ${data.currency}`, "All-in: every line already includes the platform margin. Charged hourly against wallet credit.");
275
380
  return { content: [{ type: "text", text: lines.join("\n") }] };
276
381
  });
277
382
  // --- Instance lifecycle tools ---
@@ -315,15 +420,23 @@ ENGINE-SPECIFIC FOLLOW-UPS (route to the right tools per engine after creating):
315
420
  - Performance tuning: the pg-config tools for postgres, the mysql-config tools for mysql; none for redis or clickhouse.
316
421
  - PITR applies to postgres and mysql only - not redis, not clickhouse.
317
422
  - database-users applies to postgres, mysql and clickhouse - not redis.
318
- - vector_enabled and postgresql_config are postgres-only.
319
- - db_version can be upgraded in place on clickhouse only (upgrade_clickhouse_version).`, {
423
+ - postgresql_config and the two extension flags are postgres-only.
424
+ - db_version can be upgraded in place on clickhouse only (upgrade_clickhouse_version).
425
+
426
+ POSTGRES EXTENSIONS (neither is a separate engine - both are postgres + a flag):
427
+ - timescaledb_enabled → TimescaleDB (hypertables, continuous aggregates, compression) for
428
+ time-series workloads. Installing it at create costs nothing extra; enabling it later
429
+ restarts PostgreSQL once (enable_timescaledb).
430
+ - vector_enabled → pgvector (the \`vector\` type + similarity search) for embeddings.
431
+ - Both can be on at once here, and both are ENABLE-ONLY - the backend refuses to turn
432
+ either off afterwards, so ask the user before setting them.`, {
320
433
  organization_id: z.string().optional().describe("Organization PID (defaults to active org)"),
321
434
  region: z.string().min(1, "AWS region is required (e.g. us-east-1)"),
322
435
  identifier: z.string().min(1, "Database instance name"),
323
436
  instance_type: z.string().min(1, "EC2 instance type (e.g. t3.micro, r6i.xlarge)"),
324
437
  type_of_dbms: z.enum(ENGINES).describe("Database engine"),
325
438
  storage: storageSchema,
326
- db_version: z.string().optional().describe(`Database version. postgres: ${ENGINE_VERSIONS.postgres.join(", ")}. mysql: ${ENGINE_VERSIONS.mysql.join(", ")}. redis: ${ENGINE_VERSIONS.redis.join(", ")}. clickhouse: ${ENGINE_VERSIONS.clickhouse.join(", ")} (defaults to the ${CLICKHOUSE_DEFAULT_VERSION} LTS). Other engines default to the newest.`),
439
+ db_version: z.string().optional().describe(`Database version. postgres: ${ENGINE_VERSIONS.postgres.join(", ")} (default ${ENGINE_DEFAULT_VERSION.postgres}). mysql: ${ENGINE_VERSIONS.mysql.join(", ")} (default ${ENGINE_DEFAULT_VERSION.mysql}, the stable release). redis: ${ENGINE_VERSIONS.redis.join(", ")} (default ${ENGINE_DEFAULT_VERSION.redis}). clickhouse: ${ENGINE_VERSIONS.clickhouse.join(", ")} (default ${ENGINE_DEFAULT_VERSION.clickhouse}, the LTS). Omitting this sends the default explicitly - the backend's own fallback is the oldest major it still supports.`),
327
440
  username: z.string().regex(/^[a-z_][a-z0-9_-]{0,30}$/i).optional().describe("Database username (default: admin). Redis ignores this (auth is a single requirepass/password); it applies to postgres, mysql and clickhouse. On clickhouse the customer user is `admin` - the built-in `default` user is platform-internal and not usable."),
328
441
  custom_password: z.string().min(8).max(128).optional().describe("Database password (8-128 printable ASCII chars). Auto-generated if omitted."),
329
442
  description: z.string().optional(),
@@ -337,6 +450,8 @@ ENGINE-SPECIFIC FOLLOW-UPS (route to the right tools per engine after creating):
337
450
  allowed_cidr_ranges: z.array(cidrRangeSchema).max(24).optional().describe("IP whitelist (max 24 CIDR ranges)"),
338
451
  tags: z.array(tagSchema).optional().describe("AWS resource tags"),
339
452
  snapshot_id: z.string().optional().describe("Restore from a backup snapshot ID"),
453
+ timescaledb_enabled: z.boolean().optional().describe("Install TimescaleDB for time-series data (postgres only). Enable-only - it cannot be turned off later, so confirm with the user first."),
454
+ vector_enabled: z.boolean().optional().describe("Install pgvector for embeddings / similarity search (postgres only). Enable-only - it cannot be turned off later, so confirm with the user first."),
340
455
  postgresql_config: z.record(z.unknown()).optional().describe("PostgreSQL parameter overrides (use preview_pg_config to see available params)"),
341
456
  iam_role_arn: z.string().optional().describe("IAM role ARN for the EC2 instance"),
342
457
  db_port: z.number().int().min(0).max(65535).optional().describe(`Database port (default ${ENGINE_DEFAULT_PORT.postgres} postgres, ${ENGINE_DEFAULT_PORT.mysql} mysql, ${ENGINE_DEFAULT_PORT.redis} redis, ${ENGINE_DEFAULT_PORT.clickhouse} clickhouse HTTP)`),
@@ -359,6 +474,23 @@ ENGINE-SPECIFIC FOLLOW-UPS (route to the right tools per engine after creating):
359
474
  if (params.db_version && !ENGINE_VERSIONS[engine].includes(params.db_version)) {
360
475
  return { content: [{ type: "text", text: `'${params.db_version}' is not a supported ${engine} version. Supported: ${ENGINE_VERSIONS[engine].join(", ")}.` }] };
361
476
  }
477
+ // Extension flags are postgres-only. The backend silently forces them false on other
478
+ // engines, which would look like a successful "TimescaleDB instance" that has none -
479
+ // so refuse instead of quietly dropping the request.
480
+ if (engine !== "postgres") {
481
+ const requested = [
482
+ params.timescaledb_enabled ? "timescaledb_enabled" : null,
483
+ params.vector_enabled ? "vector_enabled" : null,
484
+ ].filter(Boolean);
485
+ if (requested.length > 0) {
486
+ return {
487
+ content: [{
488
+ type: "text",
489
+ text: `${requested.join(" and ")} ${requested.length > 1 ? "are" : "is"} PostgreSQL-only and would be ignored on ${engine}. Create this as postgres to get ${requested.length > 1 ? "them" : "it"}, or drop the flag${requested.length > 1 ? "s" : ""}.${engine === "clickhouse" ? " (For analytics workloads ClickHouse is already columnar - it needs no extension.)" : ""}`,
490
+ }],
491
+ };
492
+ }
493
+ }
362
494
  // Multi-AZ topology. ClickHouse replicates through a Keeper/Raft quorum rather than
363
495
  // streaming replication, so it needs at least one replica and allows a wider fan-out.
364
496
  const multiAz = params.multi_az === true;
@@ -406,11 +538,11 @@ ENGINE-SPECIFIC FOLLOW-UPS (route to the right tools per engine after creating):
406
538
  },
407
539
  public_access: true, // Always true - private access not yet supported
408
540
  };
409
- // ClickHouse: pin the version explicitly rather than inheriting the backend's
410
- // oldest-supported fallback (24.3).
411
- const dbVersion = params.db_version ?? (engine === "clickhouse" ? CLICKHOUSE_DEFAULT_VERSION : undefined);
412
- if (dbVersion)
413
- body.db_version = dbVersion;
541
+ // Always pin the version. Left off the wire the backend falls back to the oldest
542
+ // major the adapter supports (postgres 14, redis 7.0, clickhouse 24.3), which is
543
+ // not what the console gives the same user.
544
+ const dbVersion = params.db_version ?? ENGINE_DEFAULT_VERSION[engine];
545
+ body.db_version = dbVersion;
414
546
  if (params.username)
415
547
  body.username = params.username;
416
548
  if (params.custom_password)
@@ -438,6 +570,10 @@ ENGINE-SPECIFIC FOLLOW-UPS (route to the right tools per engine after creating):
438
570
  body.tags = params.tags;
439
571
  if (params.snapshot_id)
440
572
  body.snapshot_id = params.snapshot_id;
573
+ if (params.timescaledb_enabled)
574
+ body.timescaledb_enabled = true;
575
+ if (params.vector_enabled)
576
+ body.vector_enabled = true;
441
577
  if (params.postgresql_config)
442
578
  body.postgresql_config = params.postgresql_config;
443
579
  if (params.iam_role_arn)
@@ -459,6 +595,7 @@ ENGINE-SPECIFIC FOLLOW-UPS (route to the right tools per engine after creating):
459
595
  "Instance provisioning started!",
460
596
  "",
461
597
  `PID: ${data.pid}`,
598
+ `Engine: ${engine} ${dbVersion}${params.db_version ? "" : " (default)"}`,
462
599
  `Host: ${data.host}`,
463
600
  `Username: ${data.username}`,
464
601
  `Password: ${data.password}`,
@@ -468,6 +605,20 @@ ENGINE-SPECIFIC FOLLOW-UPS (route to the right tools per engine after creating):
468
605
  `Group ID: ${data.group_id}`,
469
606
  `Organization: ${data.organization_id}`,
470
607
  ];
608
+ if (engine === "postgres" && (params.timescaledb_enabled || params.vector_enabled)) {
609
+ const extensions = [
610
+ params.timescaledb_enabled ? "TimescaleDB" : null,
611
+ params.vector_enabled ? "pgvector" : null,
612
+ ].filter(Boolean);
613
+ lines.push("", `${extensions.join(" + ")} ${extensions.length > 1 ? "are" : "is"} installed during provisioning (no restart needed at create).`);
614
+ if (params.timescaledb_enabled) {
615
+ lines.push("- Start with: CREATE TABLE ... then SELECT create_hypertable('your_table', 'time');", "- The timescaledb_* metrics (hypertable/chunk counts, compression ratio, total size) join this instance's `postgresql.database` alert set.");
616
+ }
617
+ if (params.vector_enabled) {
618
+ lines.push("- The `vector` column type and similarity operators are ready to use.");
619
+ }
620
+ lines.push(`- Neither extension can be disabled later.`);
621
+ }
471
622
  if (engine === "clickhouse") {
472
623
  lines.push("", `ClickHouse ${dbVersion}`, `- Connect over HTTP on port ${ENGINE_DEFAULT_PORT.clickhouse} (native TCP on ${CLICKHOUSE_NATIVE_PORT}).`);
473
624
  if (multiAz) {
@@ -523,7 +674,83 @@ ENGINE-SPECIFIC FOLLOW-UPS (route to the right tools per engine after creating):
523
674
  }
524
675
  return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
525
676
  });
526
- server.tool("update_instance", "Update an instance's configuration. Some changes are synchronous (db_port, backup, CIDR ranges), others are async (instance_type, multi_az, storage). Cannot update replicas - update the master instead.", {
677
+ server.tool("get_instance_metrics", `Read historical metrics for managed database instances - the same agent-heartbeat series the console graphs.
678
+
679
+ Use this to answer "how is my database doing?", to size a change before making it, or to sanity-check an alert rule (a rule on a metric that never appears here will never fire).
680
+
681
+ Returns a per-instance, per-metric summary (latest / min / max / avg + sample count) by default. Set raw=true only when you genuinely need every sample - a busy instance produces hundreds of frames per day and they will flood the context.
682
+
683
+ Metric sets (same names alert rules use):
684
+ - system: cpu_percent, memory_percent, disk_usage_percent, load_avg_1/5/15, swap_usage_percent, cpu_io_wait_percent
685
+ - network: recv_bytes_per_sec, sent_bytes_per_sec
686
+ - storage: disk_used_percent, disk_free_bytes, inodes_used_percent, read/write_io_per_second, ... reported PER MOUNT POINT and keyed \`storage[/mount]\` in the summary. Read the engine's data volume (e.g. \`storage[/selfhostdev/postgresql]\`), not \`storage[/]\` or \`storage[/boot]\` - a full /boot says nothing about database capacity.
687
+ - <engine>.database: postgresql.database | mysql.database | redis.database | clickhouse.database - the engine's own counters
688
+ - pgbouncer.stats appears once pooling is enabled. It is READABLE here but NOT alertable - the alert-rule catalog has no such metric_type, so a rule on it is rejected.
689
+
690
+ An empty result usually means the agent has not reported in that window (a stopped instance, or one still provisioning), not that the instance is idle.`, {
691
+ organization_id: z.string().optional().describe("Organization PID (defaults to active org)"),
692
+ instance_pid: z.string().optional().describe("Limit to one instance (awsinst_...). Omit for every instance in the org."),
693
+ start_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "YYYY-MM-DD").optional()
694
+ .describe("Inclusive start date, YYYY-MM-DD (default: yesterday). The window covers whole days."),
695
+ end_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "YYYY-MM-DD").optional()
696
+ .describe("Inclusive end date, YYYY-MM-DD (default: today)"),
697
+ metric_type: z.enum(["system", "network", "storage", "postgresql.database", "mysql.database", "redis.database", "clickhouse.database"]).optional()
698
+ .describe("Only return this metric set. Cheaper than filtering client-side."),
699
+ raw: z.boolean().optional().describe("Return every heartbeat frame instead of the summary. Verbose - use a narrow window and a single instance."),
700
+ }, async ({ organization_id, instance_pid, start_date, end_date, metric_type, raw }) => {
701
+ const orgId = resolveOrgId(organization_id);
702
+ if (!orgId) {
703
+ return { content: [{ type: "text", text: noOrgMessage }] };
704
+ }
705
+ const today = new Date();
706
+ const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
707
+ const asDate = (d) => d.toISOString().slice(0, 10);
708
+ const from = start_date ?? asDate(yesterday);
709
+ const to = end_date ?? asDate(today);
710
+ if (from > to) {
711
+ return { content: [{ type: "text", text: `start_date ${from} is after end_date ${to}.` }] };
712
+ }
713
+ const query = { start_date: from, end_date: to };
714
+ if (instance_pid)
715
+ query.pid = instance_pid;
716
+ if (metric_type)
717
+ query.metric_type = metric_type;
718
+ const result = await apiRequest(`/organizations/${orgId}/instance_metrics`, {
719
+ toolName: "get_instance_metrics",
720
+ query,
721
+ skipOrgInjection: true,
722
+ });
723
+ if (!result.success) {
724
+ return { content: [{ type: "text", text: `Failed to get instance metrics (${result.statusCode}): ${result.message}` }] };
725
+ }
726
+ const frames = Array.isArray(result.data) ? result.data : [];
727
+ if (frames.length === 0) {
728
+ return {
729
+ content: [{
730
+ type: "text",
731
+ text: `No metrics reported between ${from} and ${to}${instance_pid ? ` for ${instance_pid}` : ""}. The agent may not have checked in - confirm the instance is running with \`get_instance\`.`,
732
+ }],
733
+ };
734
+ }
735
+ if (raw) {
736
+ return {
737
+ content: [{ type: "text", text: JSON.stringify({ window: { start_date: from, end_date: to }, frames }, null, 2) }],
738
+ };
739
+ }
740
+ return {
741
+ content: [{
742
+ type: "text",
743
+ text: JSON.stringify({ window: { start_date: from, end_date: to }, instances: summarizeMetrics(frames) }, null, 2),
744
+ }],
745
+ };
746
+ });
747
+ // No manual failover tool on purpose. `POST /instances/:pid/trigger_failover` exists, but
748
+ // failover is an AUTOMATIC platform behaviour: InstanceHealthCheckJob promotes a replica on
749
+ // its own when the master fails (30s cooldown), and the console exposes failovers only as
750
+ // history - activity counts, a `failover_completed` state, achievement badges - with no
751
+ // button to trigger one. The endpoint is an operator escape hatch, so it stays out of an
752
+ // LLM's reach: it drops every connection and the old master never returns as master.
753
+ server.tool("update_instance", "Update an instance's configuration. Some changes are synchronous (db_port, backup, CIDR ranges), others are async (instance_type, multi_az, storage). Cannot update replicas - update the master instead. Not handled here: enabling TimescaleDB or pgvector on postgres (enable_timescaledb / enable_pgvector - both irreversible) and ClickHouse version upgrades (upgrade_clickhouse_version).", {
527
754
  instance_id: z.string().min(1, "Instance PID is required"),
528
755
  identifier: z.string().optional().describe("New instance name"),
529
756
  delete_protection: z.boolean().optional(),