@groundfloorcloud/mcp 0.1.5 → 0.1.6

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 (3) hide show
  1. package/README.md +3 -1
  2. package/dist/index.js +160 -10
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -69,7 +69,9 @@ The `command` must be your Node (from `which node`), not `npx` and not Cursor’
69
69
  |------|------|
70
70
  | `whoami` / `workspace_context` | Session + live workspace snapshot |
71
71
  | `workspaces_list` | List; switch with `gf workspaces use` |
72
- | `coderunner_list` / `_get_status` / `_get_logs` | Inspect workloads |
72
+ | `coderunner_list` / `_get_status` / `_get_logs` / `_process_log` | Inspect workloads (Process Log does not need a job_id) |
73
+ | `environments_list` | List workspace environments |
74
+ | `coderunner_cancel_deployment` | Delete current (or given) deployment |
73
75
  | `apps_list` / `apps_get` | Inspect product Apps |
74
76
  | `secrets_list` / `files_list` / `domains_list` / `dataplane_status` | Inspect (no secrets values) |
75
77
  | `docs_list` / `docs_get` | Product docs (`coderunner`, `coderunner-runtime`, and `shell` are embedded how-tos) |
package/dist/index.js CHANGED
@@ -191,6 +191,16 @@ async function cpGet(apiUrl, path2) {
191
191
  }
192
192
  return await res.json();
193
193
  }
194
+ async function cpDelete(apiUrl, path2) {
195
+ const token = await getValidAccessToken();
196
+ const res = await fetch(`${apiUrl}${path2}`, {
197
+ method: "DELETE",
198
+ headers: { Authorization: `Bearer ${token}`, Accept: "application/json" }
199
+ });
200
+ if (!res.ok) {
201
+ throw new CpError(res.status, await parseError(res));
202
+ }
203
+ }
194
204
  function queryString(params) {
195
205
  const u = new URLSearchParams();
196
206
  for (const [key, value] of Object.entries(params)) {
@@ -229,6 +239,22 @@ async function getDeployment(apiUrl, workspaceId, coderunnerId) {
229
239
  }
230
240
  return await res.json();
231
241
  }
242
+ function deleteCoderunnerDeployment(apiUrl, workspaceId, coderunnerId, deploymentId) {
243
+ return cpDelete(
244
+ apiUrl,
245
+ `/v1/workspaces/${encodeURIComponent(
246
+ workspaceId
247
+ )}/coderunners/${encodeURIComponent(
248
+ coderunnerId
249
+ )}/deployments/${encodeURIComponent(deploymentId)}`
250
+ );
251
+ }
252
+ function listWorkspaceEnvironments(apiUrl, workspaceId) {
253
+ return cpGet(
254
+ apiUrl,
255
+ `/v1/workspaces/${encodeURIComponent(workspaceId)}/environments`
256
+ );
257
+ }
232
258
  function getCoderunnerStatus(apiUrl, workspaceId, coderunnerId) {
233
259
  return cpGet(
234
260
  apiUrl,
@@ -247,6 +273,18 @@ function getCoderunnerLogs(apiUrl, workspaceId, coderunnerId, jobId) {
247
273
  )}/logs/${encodeURIComponent(jobId)}`
248
274
  );
249
275
  }
276
+ function getProcessLogs(apiUrl, workspaceId, query = {}) {
277
+ const params = new URLSearchParams();
278
+ for (const [key, value] of Object.entries(query)) {
279
+ if (value == null || value === "") continue;
280
+ params.set(key, String(value));
281
+ }
282
+ const qs = params.toString();
283
+ return cpGet(
284
+ apiUrl,
285
+ `/v1/workspaces/${encodeURIComponent(workspaceId)}/logs${qs ? `?${qs}` : ""}`
286
+ );
287
+ }
250
288
  function domainsBase(workspaceId, coderunnerId) {
251
289
  return `/v1/workspaces/${encodeURIComponent(
252
290
  workspaceId
@@ -429,7 +467,9 @@ Bind \`0.0.0.0\` and \`PORT\` (default 8080), for example
429
467
  ## Jobs and schedules
430
468
 
431
469
  Same package/start rules. The process may exit 0 when finished; it does not
432
- need a public URL. Schedules have **no** Deployment URL.
470
+ need a public URL. Schedules have **no** Deployment URL. Set cron with
471
+ \`gf deploy --workload-type schedule --schedule "<cron>"\` or
472
+ \`groundfloor.json\` \`"schedule"\`.
433
473
 
434
474
  ## What \`gf deploy\` checks
435
475
 
@@ -506,8 +546,9 @@ Pick **one** type per coderunner. Changing type later means a new coderunner.
506
546
 
507
547
  ### Schedule
508
548
 
509
- - Same as job, plus a cron expression at create/deploy (\`schedule\` /
510
- \`CronExpression\`).
549
+ - Same as job, plus a cron expression at deploy:
550
+ \`gf deploy --workload-type schedule --schedule "<cron>"\`
551
+ (or \`groundfloor.json\` \`"schedule"\`).
511
552
  - No public invoke URL. Debug with Process Log, not curl.
512
553
 
513
554
  ### Service (Deployment)
@@ -545,7 +586,7 @@ CMD ["npm", "start"]
545
586
  \`gf secrets\` / deploy \`-e\`, never in the archive.
546
587
  - Zip **contents** at root, not a wrapping folder.
547
588
  - Optional \`groundfloor.json\`: \`name\`, \`runtime\`, \`workloadType\`,
548
- \`cpu\`, \`memory\`, \`env\`.
589
+ \`cpu\`, \`memory\`, \`env\`, \`schedule\` (cron, schedule workloads only).
549
590
 
550
591
  ## Local development with live workspace data
551
592
 
@@ -747,7 +788,7 @@ function suggestCli(intent, args) {
747
788
  case "deploy":
748
789
  return {
749
790
  command: name ? `gf deploy -n ${name}${ws}` : `gf deploy${ws}`,
750
- why: "Package the current (or --path) folder and deploy as a Coderunner. Workspace Authentication (groundfloor/external) must already be active. workloadType function|job|schedule|service (service = Deployment; requires a Dockerfile). Node bootstrap: scripts.start, listen PORT||8080, bind 0.0.0.0. Do not create an App for this.",
791
+ why: 'Package the current (or --path) folder and deploy as a Coderunner. Workspace Authentication (groundfloor/external) must already be active. workloadType function|job|schedule|service (service = Deployment; requires a Dockerfile). Schedules need --schedule "<cron>" or groundfloor.json schedule. Node bootstrap: scripts.start, listen PORT||8080, bind 0.0.0.0. Do not create an App for this.',
751
792
  docs: "https://docs.groundfloor.cloud/docs/customer-portal/coderunner"
752
793
  };
753
794
  case "run":
@@ -782,6 +823,18 @@ function suggestCli(intent, args) {
782
823
  why: "Ship a Shell federated App. Always init the kit first. npm run release (not zip dist/, not a lone remoteEntry.js). Do not gf deploy. Do not invent App.tsx.",
783
824
  docs: "https://docs.groundfloor.cloud/docs/integrations/shell"
784
825
  };
826
+ case "environments":
827
+ return {
828
+ command: `gf environments ls${ws}`,
829
+ why: "List workspace environments (id, name, status) for secrets and deploy targeting.",
830
+ docs: "https://docs.groundfloor.cloud/docs/customer-portal/developers"
831
+ };
832
+ case "cancel_deployment":
833
+ return {
834
+ command: name ? `gf coderunner cancel-deployment -c ${name}${ws}` : `gf coderunner cancel-deployment -c <id-or-slug>${ws}`,
835
+ why: "Delete the current deployment (or --deployment-id). Use this for a deploy stuck in deploying.",
836
+ docs: "https://docs.groundfloor.cloud/docs/customer-portal/coderunner"
837
+ };
785
838
  case "secrets":
786
839
  return {
787
840
  command: `gf secrets ls${ws}`,
@@ -797,7 +850,7 @@ function suggestCli(intent, args) {
797
850
  default:
798
851
  return {
799
852
  command: "gf --help",
800
- why: `Unknown intent "${intent}". Use login | switch_workspace | deploy | run | status | init_app | publish_app | secrets | dataplane.`,
853
+ why: `Unknown intent "${intent}". Use login | switch_workspace | deploy | run | status | logs | environments | cancel_deployment | init_app | publish_app | secrets | dataplane.`,
801
854
  docs: "https://docs.groundfloor.cloud/docs/customer-portal/developers"
802
855
  };
803
856
  }
@@ -841,7 +894,7 @@ async function resolveCoderunnerId(apiUrl, workspaceId, idOrSlug) {
841
894
  }
842
895
  var server = new McpServer({
843
896
  name: "groundfloor",
844
- version: "0.1.5"
897
+ version: "0.1.6"
845
898
  });
846
899
  server.tool(
847
900
  "whoami",
@@ -906,7 +959,7 @@ server.tool(
906
959
  types: {
907
960
  function: "HTTP handler on PORT/8080 bound to 0.0.0.0. Invoke with gf coderunner run or the Deployment URL. Node requires package.json scripts.start.",
908
961
  job: "Run-to-completion worker. Deploy, then gf coderunner run.",
909
- schedule: "Cron job. No public Deployment URL. Set schedule/cron at create or deploy.",
962
+ schedule: 'Cron job. No public Deployment URL. Set cron with gf deploy --workload-type schedule --schedule "<cron>" (or groundfloor.json schedule).',
910
963
  service: "Long-running Deployment (same type as service). Dockerfile at ZIP root is required. Listen on 8080 / 0.0.0.0."
911
964
  },
912
965
  node: "No Dockerfile \u2192 platform injects node:18-alpine CMD npm start EXPOSE 8080. Require scripts.start; listen on PORT||8080; bind 0.0.0.0. gf deploy fails fast if scripts.start is missing.",
@@ -922,6 +975,8 @@ server.tool(
922
975
  switch_workspace: suggestCli("switch_workspace"),
923
976
  run: suggestCli("run"),
924
977
  logs: suggestCli("logs"),
978
+ environments: suggestCli("environments"),
979
+ cancel_deployment: suggestCli("cancel_deployment"),
925
980
  init_app: suggestCli("init_app"),
926
981
  publish_app: suggestCli("publish_app")
927
982
  },
@@ -997,9 +1052,102 @@ server.tool(
997
1052
  }
998
1053
  }
999
1054
  );
1055
+ server.tool(
1056
+ "coderunner_process_log",
1057
+ "Process Log (deploy/build stdout/stderr) by coderunner id. Prefer this over coderunner_get_logs when there is no job_id.",
1058
+ {
1059
+ workspace_id: wsId,
1060
+ coderunner_id: z.string().min(1).describe("Coderunner id or slug"),
1061
+ limit: z.number().int().positive().optional(),
1062
+ since_seconds: z.number().int().min(10).optional(),
1063
+ search: z.string().optional(),
1064
+ deployment_id: z.string().optional()
1065
+ },
1066
+ async (args) => {
1067
+ try {
1068
+ const c = await cfg(args.workspace_id);
1069
+ const id = await resolveCoderunnerId(
1070
+ c.apiUrl,
1071
+ c.workspaceId,
1072
+ args.coderunner_id
1073
+ );
1074
+ return text(
1075
+ await getProcessLogs(c.apiUrl, c.workspaceId, {
1076
+ runner_id: id,
1077
+ deployment_id: args.deployment_id,
1078
+ limit: args.limit ?? 50,
1079
+ since_seconds: args.since_seconds ?? 3600,
1080
+ search: args.search
1081
+ })
1082
+ );
1083
+ } catch (e) {
1084
+ return errText(e);
1085
+ }
1086
+ }
1087
+ );
1088
+ server.tool(
1089
+ "environments_list",
1090
+ "List environments in the workspace (id, name, status).",
1091
+ { workspace_id: wsId },
1092
+ async ({ workspace_id }) => {
1093
+ try {
1094
+ const c = await cfg(workspace_id);
1095
+ const environments = await listWorkspaceEnvironments(
1096
+ c.apiUrl,
1097
+ c.workspaceId
1098
+ );
1099
+ return text({ environments, count: environments.length });
1100
+ } catch (e) {
1101
+ return errText(e);
1102
+ }
1103
+ }
1104
+ );
1105
+ server.tool(
1106
+ "coderunner_cancel_deployment",
1107
+ "Delete a coderunner deployment (current deployment if deployment_id is omitted). Same as `gf coderunner cancel-deployment`.",
1108
+ {
1109
+ workspace_id: wsId,
1110
+ coderunner_id: z.string().min(1).describe("Coderunner id or slug"),
1111
+ deployment_id: z.string().optional().describe("Deployment id; defaults to the current deployment")
1112
+ },
1113
+ async (args) => {
1114
+ try {
1115
+ const c = await cfg(args.workspace_id);
1116
+ const id = await resolveCoderunnerId(
1117
+ c.apiUrl,
1118
+ c.workspaceId,
1119
+ args.coderunner_id
1120
+ );
1121
+ let deploymentId = args.deployment_id?.trim();
1122
+ if (!deploymentId) {
1123
+ const current = await getDeployment(c.apiUrl, c.workspaceId, id);
1124
+ deploymentId = current?.id?.trim();
1125
+ }
1126
+ if (!deploymentId) {
1127
+ throw new Error(
1128
+ `No current deployment for coderunner ${id}. Pass deployment_id.`
1129
+ );
1130
+ }
1131
+ await deleteCoderunnerDeployment(
1132
+ c.apiUrl,
1133
+ c.workspaceId,
1134
+ id,
1135
+ deploymentId
1136
+ );
1137
+ return text({
1138
+ coderunner_id: id,
1139
+ deployment_id: deploymentId,
1140
+ deleted: true,
1141
+ next: suggestCli("logs", { coderunner: id })
1142
+ });
1143
+ } catch (e) {
1144
+ return errText(e);
1145
+ }
1146
+ }
1147
+ );
1000
1148
  server.tool(
1001
1149
  "coderunner_get_logs",
1002
- "Job-run logs from a previous `gf coderunner run` (needs job_id). For deploy/build stdout use suggest_cli intent logs (`gf coderunner logs`).",
1150
+ "Job-run logs from a previous `gf coderunner run` (needs job_id). For deploy/build stdout use coderunner_process_log or suggest_cli intent logs (`gf coderunner logs`).",
1003
1151
  {
1004
1152
  workspace_id: wsId,
1005
1153
  coderunner_id: z.string().min(1),
@@ -1160,13 +1308,15 @@ server.tool(
1160
1308
  "run",
1161
1309
  "status",
1162
1310
  "logs",
1311
+ "environments",
1312
+ "cancel_deployment",
1163
1313
  "init_app",
1164
1314
  "publish_app",
1165
1315
  "secrets",
1166
1316
  "dataplane"
1167
1317
  ]),
1168
1318
  workspace: z.string().optional(),
1169
- name: z.string().optional().describe("Coderunner name/id for deploy/run/status/logs"),
1319
+ name: z.string().optional().describe("Coderunner name/id for deploy/run/status/logs/cancel_deployment"),
1170
1320
  slug: z.string().optional().describe("Portal app slug for init_app / publish_app"),
1171
1321
  cell: z.enum(["production", "stage", "dev"]).optional()
1172
1322
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groundfloorcloud/mcp",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Groundfloor MCP — inspect workspace state and docs; take actions with the gf CLI.",
5
5
  "type": "module",
6
6
  "bin": {