@hoststack.dev/mcp 0.7.0 → 0.9.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/dist/hoststack-mcp.js +93 -0
- package/dist/hoststack-mcp.js.map +1 -1
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/hoststack-mcp.js
CHANGED
|
@@ -680,6 +680,99 @@ defineTool({
|
|
|
680
680
|
return respond({ summary: `Database ${args2.database_id} (${type}) is ${status}.`, data });
|
|
681
681
|
}
|
|
682
682
|
});
|
|
683
|
+
defineTool({
|
|
684
|
+
name: "query_database",
|
|
685
|
+
category: "databases",
|
|
686
|
+
description: [
|
|
687
|
+
"Run a single READ-ONLY SQL statement against a managed Postgres database.",
|
|
688
|
+
"",
|
|
689
|
+
"When to use: ad-hoc data inspection \u2014 checking row counts, sampling rows, debugging a constraint. For schema changes or seed data, ship a Drizzle migration instead.",
|
|
690
|
+
"",
|
|
691
|
+
"Safety model (server-side, not bypassable):",
|
|
692
|
+
' - Wrapped in `BEGIN TRANSACTION READ ONLY` \u2014 INSERT/UPDATE/DELETE/DDL fail with a clear "cannot execute \u2026 in a read-only transaction" error.',
|
|
693
|
+
" - 30s statement_timeout \u2014 runaway queries are killed.",
|
|
694
|
+
" - 1000-row cap; result CSV is also size-capped at 1 MiB. Both surface via `truncated: true`.",
|
|
695
|
+
" - Postgres engine only (v1). Other engines return a 400.",
|
|
696
|
+
" - Single statement only; no embedded `;` and no psql meta-commands (`\\\u2026`).",
|
|
697
|
+
' - Every call is audit-logged (success or failure) as `database.query` so an admin can answer "who queried this DB, when, with what SQL".',
|
|
698
|
+
"",
|
|
699
|
+
"Inputs:",
|
|
700
|
+
` - database_id: publicId of the postgres database (e.g. "db_\u2026"). Resolved via the SDK's publicId helper.`,
|
|
701
|
+
" - sql: a single SELECT/WITH/SHOW/EXPLAIN statement, no trailing `;`. Max 16 KiB.",
|
|
702
|
+
"",
|
|
703
|
+
"Returns: { columns: string[], rows: string[][], rowCount: number, truncated: boolean, durationMs: number }. Values come back as strings (psql --csv); cast on the caller side when needed.",
|
|
704
|
+
"",
|
|
705
|
+
"Example:",
|
|
706
|
+
' - query_database({ database_id: "db_abc", sql: "SELECT count(*) FROM users" }) \u2192 { columns: ["count"], rows: [["1234"]], \u2026 }',
|
|
707
|
+
` - query_database({ database_id: "db_abc", sql: "SELECT id, email FROM users WHERE created_at > now() - interval '1 day' LIMIT 50" })`
|
|
708
|
+
].join("\n"),
|
|
709
|
+
input: {
|
|
710
|
+
database_id: z5.string().describe("Database publicId (e.g. db_abc)."),
|
|
711
|
+
sql: z5.string().min(1).max(16384).describe(
|
|
712
|
+
"A single READ-ONLY SQL statement. No trailing `;`, no embedded `;`, no psql meta-commands."
|
|
713
|
+
)
|
|
714
|
+
},
|
|
715
|
+
handler: async (args2, ctx) => {
|
|
716
|
+
const teamId = await ctx.resolveTeamId();
|
|
717
|
+
const result = await ctx.hoststack.databases.query(teamId, args2.database_id, args2.sql);
|
|
718
|
+
const summary = result.truncated ? `Query returned ${result.rowCount} row${result.rowCount === 1 ? "" : "s"} (truncated; raise LIMIT if needed) in ${result.durationMs}ms.` : `Query returned ${result.rowCount} row${result.rowCount === 1 ? "" : "s"} in ${result.durationMs}ms.`;
|
|
719
|
+
return respond({ summary, data: result });
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
defineTool({
|
|
723
|
+
name: "upgrade_database_to_ha",
|
|
724
|
+
category: "databases",
|
|
725
|
+
description: [
|
|
726
|
+
"Trigger a single-node \u2192 HA migration on a managed Postgres database. The agent runs pg_dump \u2192 bootstrap 3-node Patroni cluster (1 leader + 2 sync replicas + HAProxy) \u2192 pg_restore. Customer connections to this database are briefly read-only during the cutover; the standalone container stays running (read-only) for 24 h as a rollback target before automatic decommission.",
|
|
727
|
+
"",
|
|
728
|
+
"Requires: PATRONI_ENABLED env on the HostStack deployment + `teams.ha_beta = true` on the calling team. Returns 403 otherwise. Returns 400 if the database is already HA, not Postgres, not on the standard or pro tier, or has a pg_database_size over 1 GB (in-memory archive transfer cap).",
|
|
729
|
+
"",
|
|
730
|
+
"When to use: a customer needs failover protection on an existing standalone Postgres. For new databases on an HA-eligible team, just call create_database \u2014 the cluster path is automatic.",
|
|
731
|
+
"",
|
|
732
|
+
"Inputs:",
|
|
733
|
+
" - database_id: publicId of the postgres database to upgrade.",
|
|
734
|
+
"",
|
|
735
|
+
'Returns: 202 Accepted. The upgrade is async \u2014 poll get_database until `pg_engine_type === "patroni"` and `status === "available"` to confirm completion. The Cluster tab on the dashboard mirrors the same data.',
|
|
736
|
+
"",
|
|
737
|
+
'Example: upgrade_database_to_ha({ database_id: "db_abc" }) \u2192 { ok: true }'
|
|
738
|
+
].join("\n"),
|
|
739
|
+
input: {
|
|
740
|
+
database_id: z5.string().describe("Database publicId (e.g. db_abc) to upgrade to HA.")
|
|
741
|
+
},
|
|
742
|
+
handler: async (args2, ctx) => {
|
|
743
|
+
const teamId = await ctx.resolveTeamId();
|
|
744
|
+
await ctx.hoststack.databases.upgradeToHa(teamId, args2.database_id);
|
|
745
|
+
return respond({
|
|
746
|
+
summary: `HA migration started for ${args2.database_id}. Poll get_database until pg_engine_type=patroni to confirm.`
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
defineTool({
|
|
751
|
+
name: "get_database_cluster",
|
|
752
|
+
category: "databases",
|
|
753
|
+
description: [
|
|
754
|
+
"Get the live + retired cluster members and failover history for a Patroni HA Postgres database.",
|
|
755
|
+
"",
|
|
756
|
+
"When to use: confirm an HA cluster is healthy (1 leader + 2 sync replicas + 1 proxy + 1 etcd live) or investigate a failover that fired. For standalone databases this returns 400 \u2014 call get_database first if you need to branch.",
|
|
757
|
+
"",
|
|
758
|
+
"Inputs:",
|
|
759
|
+
" - database_id: publicId of the postgres database (must be HA).",
|
|
760
|
+
"",
|
|
761
|
+
"Returns: { members: { id, memberRole: 'etcd' | 'primary-candidate' | 'replica' | 'proxy', containerId, workerHostId, joinedAt, leftAt }[], failovers: { id, createdAt, oldLeader, newLeader }[] }.",
|
|
762
|
+
"",
|
|
763
|
+
'Example: get_database_cluster({ database_id: "db_abc" }) \u2192 { members: [...5 live rows], failovers: [] }'
|
|
764
|
+
].join("\n"),
|
|
765
|
+
input: {
|
|
766
|
+
database_id: z5.string().describe("Database publicId for the HA cluster.")
|
|
767
|
+
},
|
|
768
|
+
handler: async (args2, ctx) => {
|
|
769
|
+
const teamId = await ctx.resolveTeamId();
|
|
770
|
+
const result = await ctx.hoststack.databases.getCluster(teamId, args2.database_id);
|
|
771
|
+
const live = result.members.filter((m) => m.leftAt === null);
|
|
772
|
+
const summary = `${live.length} live members (${live.map((m) => m.memberRole).join(", ")}); ${result.failovers.length} historical failovers.`;
|
|
773
|
+
return respond({ summary, data: result });
|
|
774
|
+
}
|
|
775
|
+
});
|
|
683
776
|
|
|
684
777
|
// src/tools/deploys.ts
|
|
685
778
|
import { z as z6 } from "zod";
|