@hoststack.dev/mcp 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/dist/hoststack-mcp.js +83 -23
- package/dist/hoststack-mcp.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +82 -22
- package/dist/index.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -83,7 +83,7 @@ interface CreateServerOptions {
|
|
|
83
83
|
onToolCall?: ToolCallSink;
|
|
84
84
|
}
|
|
85
85
|
declare const PACKAGE_NAME = "hoststack";
|
|
86
|
-
declare const PACKAGE_VERSION = "0.
|
|
86
|
+
declare const PACKAGE_VERSION = "0.9.1";
|
|
87
87
|
/**
|
|
88
88
|
* Build a fully-wired MCP server. Used by both the stdio CLI entry-point and
|
|
89
89
|
* the HTTP transport mounted by the API server. A new server (and its
|
package/dist/index.js
CHANGED
|
@@ -706,7 +706,7 @@ defineTool({
|
|
|
706
706
|
"",
|
|
707
707
|
"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.",
|
|
708
708
|
"",
|
|
709
|
-
"
|
|
709
|
+
"Example:",
|
|
710
710
|
' - query_database({ database_id: "db_abc", sql: "SELECT count(*) FROM users" }) \u2192 { columns: ["count"], rows: [["1234"]], \u2026 }',
|
|
711
711
|
` - query_database({ database_id: "db_abc", sql: "SELECT id, email FROM users WHERE created_at > now() - interval '1 day' LIMIT 50" })`
|
|
712
712
|
].join("\n"),
|
|
@@ -723,6 +723,60 @@ defineTool({
|
|
|
723
723
|
return respond({ summary, data: result });
|
|
724
724
|
}
|
|
725
725
|
});
|
|
726
|
+
defineTool({
|
|
727
|
+
name: "upgrade_database_to_ha",
|
|
728
|
+
category: "databases",
|
|
729
|
+
description: [
|
|
730
|
+
"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.",
|
|
731
|
+
"",
|
|
732
|
+
"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).",
|
|
733
|
+
"",
|
|
734
|
+
"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.",
|
|
735
|
+
"",
|
|
736
|
+
"Inputs:",
|
|
737
|
+
" - database_id: publicId of the postgres database to upgrade.",
|
|
738
|
+
"",
|
|
739
|
+
'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.',
|
|
740
|
+
"",
|
|
741
|
+
'Example: upgrade_database_to_ha({ database_id: "db_abc" }) \u2192 { ok: true }'
|
|
742
|
+
].join("\n"),
|
|
743
|
+
input: {
|
|
744
|
+
database_id: z5.string().describe("Database publicId (e.g. db_abc) to upgrade to HA.")
|
|
745
|
+
},
|
|
746
|
+
handler: async (args, ctx) => {
|
|
747
|
+
const teamId = await ctx.resolveTeamId();
|
|
748
|
+
await ctx.hoststack.databases.upgradeToHa(teamId, args.database_id);
|
|
749
|
+
return respond({
|
|
750
|
+
summary: `HA migration started for ${args.database_id}. Poll get_database until pg_engine_type=patroni to confirm.`
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
defineTool({
|
|
755
|
+
name: "get_database_cluster",
|
|
756
|
+
category: "databases",
|
|
757
|
+
description: [
|
|
758
|
+
"Get the live + retired cluster members and failover history for a Patroni HA Postgres database.",
|
|
759
|
+
"",
|
|
760
|
+
"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.",
|
|
761
|
+
"",
|
|
762
|
+
"Inputs:",
|
|
763
|
+
" - database_id: publicId of the postgres database (must be HA).",
|
|
764
|
+
"",
|
|
765
|
+
"Returns: { members: { id, memberRole: 'etcd' | 'primary-candidate' | 'replica' | 'proxy', containerId, workerHostId, joinedAt, leftAt }[], failovers: { id, createdAt, oldLeader, newLeader }[] }.",
|
|
766
|
+
"",
|
|
767
|
+
'Example: get_database_cluster({ database_id: "db_abc" }) \u2192 { members: [...5 live rows], failovers: [] }'
|
|
768
|
+
].join("\n"),
|
|
769
|
+
input: {
|
|
770
|
+
database_id: z5.string().describe("Database publicId for the HA cluster.")
|
|
771
|
+
},
|
|
772
|
+
handler: async (args, ctx) => {
|
|
773
|
+
const teamId = await ctx.resolveTeamId();
|
|
774
|
+
const result = await ctx.hoststack.databases.getCluster(teamId, args.database_id);
|
|
775
|
+
const live = result.members.filter((m) => m.leftAt === null);
|
|
776
|
+
const summary = `${live.length} live members (${live.map((m) => m.memberRole).join(", ")}); ${result.failovers.length} historical failovers.`;
|
|
777
|
+
return respond({ summary, data: result });
|
|
778
|
+
}
|
|
779
|
+
});
|
|
726
780
|
|
|
727
781
|
// src/tools/deploys.ts
|
|
728
782
|
import { z as z6 } from "zod";
|
|
@@ -784,13 +838,14 @@ defineTool({
|
|
|
784
838
|
name: "trigger_deploy",
|
|
785
839
|
category: "deploys",
|
|
786
840
|
description: [
|
|
787
|
-
"Kick off a new deploy
|
|
841
|
+
"Kick off a new deploy. Defaults to the latest commit on the service branch; pass commit_hash or branch to deploy something else.",
|
|
788
842
|
"",
|
|
789
|
-
'When to use: the user explicitly asks to deploy ("ship it", "redeploy", "kick off a new build"). For services with auto-deploy enabled, a fresh git push already triggers a deploy automatically \u2014 only call this for manual triggers
|
|
843
|
+
'When to use: the user explicitly asks to deploy ("ship it", "redeploy", "kick off a new build"). For services with auto-deploy enabled, a fresh git push already triggers a deploy automatically \u2014 only call this for manual triggers or after a config change.',
|
|
790
844
|
"",
|
|
791
845
|
"Inputs:",
|
|
792
846
|
" - service_id: publicId of the service.",
|
|
793
|
-
" -
|
|
847
|
+
" - commit_hash (optional): build a specific commit instead of HEAD on the configured branch.",
|
|
848
|
+
" - branch (optional): build the tip of a non-default branch.",
|
|
794
849
|
"",
|
|
795
850
|
'Returns: { deploy: Deploy } \u2014 the new deploy record. Status will start as "pending" then transition through "building" \u2192 "deploying" \u2192 "live" or "failed".',
|
|
796
851
|
"",
|
|
@@ -798,12 +853,14 @@ defineTool({
|
|
|
798
853
|
].join("\n"),
|
|
799
854
|
input: {
|
|
800
855
|
service_id: z6.string().describe("Service publicId."),
|
|
801
|
-
|
|
856
|
+
commit_hash: z6.string().max(40).optional().describe("Specific commit to build (default: branch HEAD)."),
|
|
857
|
+
branch: z6.string().max(200).optional().describe("Branch to build (default: service configured branch).")
|
|
802
858
|
},
|
|
803
859
|
handler: async (args, ctx) => {
|
|
804
860
|
const teamId = await ctx.resolveTeamId();
|
|
805
861
|
const input = {};
|
|
806
|
-
if (args.
|
|
862
|
+
if (args.commit_hash !== void 0) input.commitHash = args.commit_hash;
|
|
863
|
+
if (args.branch !== void 0) input.branch = args.branch;
|
|
807
864
|
const response = await ctx.hoststack.deploys.trigger(teamId, args.service_id, input);
|
|
808
865
|
const data = { deploy: shapeDeploy(response.deploy) };
|
|
809
866
|
const publicId = data.deploy && "publicId" in data.deploy ? data.deploy.publicId : "unknown";
|
|
@@ -846,7 +903,7 @@ defineTool({
|
|
|
846
903
|
name: "get_deploy_logs",
|
|
847
904
|
category: "deploys",
|
|
848
905
|
description: [
|
|
849
|
-
"Fetch the build/deploy logs for a single deploy. Returns the entire log buffer the agent can
|
|
906
|
+
"Fetch the build/deploy logs for a single deploy. Returns the entire log buffer the agent can then search for errors.",
|
|
850
907
|
"",
|
|
851
908
|
"When to use: a deploy failed and you need to read the build output to diagnose. Pair with get_deploy to find a failed deploy_id, then call this. For runtime (post-deploy) logs of the running container, use get_service_logs instead.",
|
|
852
909
|
"",
|
|
@@ -1287,7 +1344,8 @@ defineTool({
|
|
|
1287
1344
|
"",
|
|
1288
1345
|
"Inputs:",
|
|
1289
1346
|
' - hostname: the domain to add (e.g. "api.example.com").',
|
|
1290
|
-
" - service_id
|
|
1347
|
+
" - service_id: publicId of the service to bind the domain to.",
|
|
1348
|
+
" - path_prefix (optional): when set, only requests under this URL prefix route to this service. Use for path-based fan-out (e.g. `/api` to an api service, `/` to a web service on the same hostname).",
|
|
1291
1349
|
"",
|
|
1292
1350
|
"Returns: { domain: Domain } \u2014 includes dnsTargets you must configure (CNAME / A records).",
|
|
1293
1351
|
"",
|
|
@@ -1295,12 +1353,16 @@ defineTool({
|
|
|
1295
1353
|
].join("\n"),
|
|
1296
1354
|
input: {
|
|
1297
1355
|
hostname: z8.string().min(3).max(253).describe("Fully-qualified hostname (e.g. api.example.com)."),
|
|
1298
|
-
service_id: z8.string().
|
|
1356
|
+
service_id: z8.string().describe("Service publicId to bind to."),
|
|
1357
|
+
path_prefix: z8.string().optional().describe('Optional URL prefix for path-based routing (e.g. "/api").')
|
|
1299
1358
|
},
|
|
1300
1359
|
handler: async (args, ctx) => {
|
|
1301
1360
|
const teamId = await ctx.resolveTeamId();
|
|
1302
|
-
const input = {
|
|
1303
|
-
|
|
1361
|
+
const input = {
|
|
1362
|
+
domain: args.hostname,
|
|
1363
|
+
serviceId: args.service_id
|
|
1364
|
+
};
|
|
1365
|
+
if (args.path_prefix !== void 0) input.pathPrefix = args.path_prefix;
|
|
1304
1366
|
const response = await ctx.hoststack.domains.add(teamId, input);
|
|
1305
1367
|
const data = { domain: shapeDomain(response.domain) };
|
|
1306
1368
|
return respond({
|
|
@@ -1401,7 +1463,7 @@ defineTool({
|
|
|
1401
1463
|
" - service_id: publicId of the service.",
|
|
1402
1464
|
' - key: env-var name (e.g. "DATABASE_URL").',
|
|
1403
1465
|
" - value: new value (will be encrypted at rest if is_secret=true).",
|
|
1404
|
-
" - is_secret (optional): true marks the value as secret (masked on read).
|
|
1466
|
+
" - is_secret (optional): true marks the value as secret (masked on read). On create, defaults to true for safety. On update, omitting it leaves the existing flag untouched \u2014 pass it explicitly only when you want to change classification.",
|
|
1405
1467
|
"",
|
|
1406
1468
|
'Returns: { envVar: EnvVar, action: "created" | "updated" }.',
|
|
1407
1469
|
"",
|
|
@@ -1415,18 +1477,16 @@ defineTool({
|
|
|
1415
1477
|
},
|
|
1416
1478
|
handler: async (args, ctx) => {
|
|
1417
1479
|
const teamId = await ctx.resolveTeamId();
|
|
1418
|
-
const isSecret = args.is_secret ?? true;
|
|
1419
1480
|
const existing = await ctx.hoststack.envVars.list(teamId, args.service_id);
|
|
1420
1481
|
const match = existing.envVars.find((v) => v.key === args.key);
|
|
1421
1482
|
if (match) {
|
|
1483
|
+
const updatePayload = { value: args.value };
|
|
1484
|
+
if (args.is_secret !== void 0) updatePayload.isSecret = args.is_secret;
|
|
1422
1485
|
const response2 = await ctx.hoststack.envVars.update(
|
|
1423
1486
|
teamId,
|
|
1424
1487
|
args.service_id,
|
|
1425
1488
|
String(match.id),
|
|
1426
|
-
|
|
1427
|
-
value: args.value,
|
|
1428
|
-
isSecret
|
|
1429
|
-
}
|
|
1489
|
+
updatePayload
|
|
1430
1490
|
);
|
|
1431
1491
|
const data2 = {
|
|
1432
1492
|
envVar: shapeEnvVar(response2.envVar),
|
|
@@ -1437,7 +1497,7 @@ defineTool({
|
|
|
1437
1497
|
const response = await ctx.hoststack.envVars.create(teamId, args.service_id, {
|
|
1438
1498
|
key: args.key,
|
|
1439
1499
|
value: args.value,
|
|
1440
|
-
isSecret
|
|
1500
|
+
isSecret: args.is_secret ?? true
|
|
1441
1501
|
});
|
|
1442
1502
|
const data = {
|
|
1443
1503
|
envVar: shapeEnvVar(response.envVar),
|
|
@@ -1573,7 +1633,7 @@ defineTool({
|
|
|
1573
1633
|
"",
|
|
1574
1634
|
"Returns: { environment: Environment }.",
|
|
1575
1635
|
"",
|
|
1576
|
-
'Example: create_environment({ project_id: "prj_abc", name: "Staging", type: "staging" }) \u2192 { environment: { id: 7, publicId: "
|
|
1636
|
+
'Example: create_environment({ project_id: "prj_abc", name: "Staging", type: "staging" }) \u2192 { environment: { id: 7, publicId: "env_\u2026", name: "Staging", type: "staging" } }'
|
|
1577
1637
|
].join("\n"),
|
|
1578
1638
|
input: {
|
|
1579
1639
|
project_id: z10.string().describe("Project publicId."),
|
|
@@ -1610,7 +1670,7 @@ defineTool({
|
|
|
1610
1670
|
"",
|
|
1611
1671
|
"Returns: { success: true } on success.",
|
|
1612
1672
|
"",
|
|
1613
|
-
'Example: delete_environment({ project_id: "prj_abc", environment_id: "
|
|
1673
|
+
'Example: delete_environment({ project_id: "prj_abc", environment_id: "env_xyz" }) \u2192 { success: true }'
|
|
1614
1674
|
].join("\n"),
|
|
1615
1675
|
input: {
|
|
1616
1676
|
project_id: z10.string().describe("Project publicId."),
|
|
@@ -1642,7 +1702,7 @@ defineTool({
|
|
|
1642
1702
|
"",
|
|
1643
1703
|
"Returns: { deploy: Deploy } \u2014 the new deploy on the target service.",
|
|
1644
1704
|
"",
|
|
1645
|
-
'Example: promote_deploy({ service_id: "svc_staging", deploy_id: "dpl_built", target_environment_id: "
|
|
1705
|
+
'Example: promote_deploy({ service_id: "svc_staging", deploy_id: "dpl_built", target_environment_id: "env_prod" }) \u2192 { deploy: { id: 99, status: "pending", trigger: "rollback", dockerImageId: "sha256:\u2026" } }'
|
|
1646
1706
|
].join("\n"),
|
|
1647
1707
|
input: {
|
|
1648
1708
|
service_id: z10.string().describe("Source service publicId."),
|
|
@@ -2664,7 +2724,7 @@ defineTool({
|
|
|
2664
2724
|
|
|
2665
2725
|
// src/server-factory.ts
|
|
2666
2726
|
var PACKAGE_NAME = "hoststack";
|
|
2667
|
-
var PACKAGE_VERSION = "0.
|
|
2727
|
+
var PACKAGE_VERSION = "0.9.1";
|
|
2668
2728
|
function createMcpServer(options) {
|
|
2669
2729
|
const baseUrl = (options.baseUrl ?? "https://hoststack.dev").replace(/\/$/, "");
|
|
2670
2730
|
const hoststack = new HostStack({ apiKey: options.apiKey, baseUrl });
|