@groundfloorcloud/mcp 0.1.3 → 0.1.5

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 +9 -4
  2. package/dist/index.js +104 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -7,6 +7,9 @@ They are not replacements. Sign in with `gf`; the MCP reads that session and
7
7
  docs so the agent knows *what is in the workspace* and *which `gf` command to
8
8
  run*.
9
9
 
10
+ Use this package (`@groundfloorcloud/mcp`). Do **not** install
11
+ `@groundfloor/coderunner-mcp` — it is not published and is not the product MCP.
12
+
10
13
  ## How the MCP gets context
11
14
 
12
15
  | Source | What it answers |
@@ -18,11 +21,11 @@ run*.
18
21
 
19
22
  Switch workspace: `gf workspaces use <uuid>` (then MCP sees it). Expired session: `gf login` again.
20
23
 
21
- ## Cursor config
24
+ ## Config
22
25
 
23
26
  `gf` must be on `PATH` and you must have run `gf login` (and usually `gf workspaces use`).
24
27
 
25
- Clients that spawn a real Node (Claude Code, some IDEs) can use:
28
+ Clients that spawn a real Node (Claude Code, Codex, many IDEs) can use:
26
29
 
27
30
  ```json
28
31
  {
@@ -35,6 +38,8 @@ Clients that spawn a real Node (Claude Code, some IDEs) can use:
35
38
  }
36
39
  ```
37
40
 
41
+ The Shell starter-kit ships this as `.mcp.json`.
42
+
38
43
  **Cursor on macOS:** do not use bare `npx`. Cursor runs npm with its bundled Node, which then looks for `Cursor.app/.../resources/lib` and fails with `ENOENT`. Install the package with *your* Node, then point MCP at that binary:
39
44
 
40
45
  ```bash
@@ -67,7 +72,7 @@ The `command` must be your Node (from `which node`), not `npx` and not Cursor’
67
72
  | `coderunner_list` / `_get_status` / `_get_logs` | Inspect workloads |
68
73
  | `apps_list` / `apps_get` | Inspect product Apps |
69
74
  | `secrets_list` / `files_list` / `domains_list` / `dataplane_status` | Inspect (no secrets values) |
70
- | `docs_list` / `docs_get` | Product docs (`coderunner` and `shell` are embedded how-tos) |
75
+ | `docs_list` / `docs_get` | Product docs (`coderunner`, `coderunner-runtime`, and `shell` are embedded how-tos) |
71
76
  | `suggest_cli` | Command to run: login, deploy, init_app, publish_app, … |
72
77
 
73
78
  Never create `app_kind=coderunner`. Deploy with `gf deploy`.
@@ -82,7 +87,7 @@ then `npm run release` and `gf apps publish --path release.zip`. Do not invent
82
87
  - Types: `function`, `job`, `schedule`, `service` (**also called Deployment**)
83
88
  - **Authentication must be active** before creating a coderunner
84
89
  - **Dockerfile is required** for `service` / Deployment
85
- - Node `scripts.start` + listen on 8080
90
+ - Runtime contract: Node `scripts.start`, listen on `PORT`/8080, bind `0.0.0.0` (`docs_get` id `coderunner-runtime`)
86
91
  - Local development against live vault/files/secrets (`gf env` + `gf token`)
87
92
 
88
93
  `workspace_context` includes a `coderunner_howto` summary so agents see this
package/dist/index.js CHANGED
@@ -380,6 +380,71 @@ async function resolveMcpConfig(overrides) {
380
380
  return { apiUrl, workspaceId, token };
381
381
  }
382
382
 
383
+ // src/coderunner-runtime.ts
384
+ var CODERUNNER_RUNTIME_BODY = `
385
+ When a ZIP has **no Dockerfile**, Coderunner injects a language bootstrap image.
386
+ Your process must match that image \u2014 \`gf deploy\` checks the Node bits before
387
+ upload. A custom Dockerfile (required for \`service\` / Deployment) replaces
388
+ the bootstrap; the listen/bind rules still apply.
389
+
390
+ ## Node (no Dockerfile)
391
+
392
+ Bootstrap is effectively:
393
+
394
+ \`\`\`dockerfile
395
+ FROM node:18-alpine
396
+ WORKDIR /app
397
+ COPY . .
398
+ EXPOSE 8080
399
+ ENV PORT=8080
400
+ CMD ["npm", "start"]
401
+ \`\`\`
402
+
403
+ 1. \`package.json\` **must** define \`"scripts": { "start": "node <entrypoint>" }\`.
404
+ Missing \`start\` \u2192 container crash loop, probe \`127.0.0.1:8080\` connection
405
+ refused, deployment **Failed (status 4)** after several minutes.
406
+ 2. Listen on \`Number(process.env.PORT || 8080)\`.
407
+ 3. Bind **\`0.0.0.0\`**, not \`127.0.0.1\`. Cluster traffic is not loopback.
408
+
409
+ \`\`\`js
410
+ const port = Number(process.env.PORT || 8080);
411
+ server.listen(port, "0.0.0.0", () => {
412
+ console.log(\`listening on 0.0.0.0:\${port}\`);
413
+ });
414
+ \`\`\`
415
+
416
+ Offer GET \`/\` or \`/health\` for the probe. Sample: \`samples/hello-fn/\`.
417
+
418
+ ## Node (with Dockerfile)
419
+
420
+ You own \`CMD\`. If it is \`npm start\`, you still need \`scripts.start\`.
421
+ Listen on 8080 / \`PORT\` and bind \`0.0.0.0\`. \`service\` (Deployment)
422
+ **requires** a root Dockerfile \u2014 bootstrap is not injected on that path.
423
+
424
+ ## Python
425
+
426
+ Bind \`0.0.0.0\` and \`PORT\` (default 8080), for example
427
+ \`uvicorn main:app --host 0.0.0.0 --port \${PORT:-8080}\`.
428
+
429
+ ## Jobs and schedules
430
+
431
+ Same package/start rules. The process may exit 0 when finished; it does not
432
+ need a public URL. Schedules have **no** Deployment URL.
433
+
434
+ ## What \`gf deploy\` checks
435
+
436
+ - \`service\` without a root Dockerfile \u2192 fail immediately.
437
+ - Node without a Dockerfile (or a Dockerfile that runs \`npm start\`) and no
438
+ \`scripts.start\` \u2192 fail immediately with this contract.
439
+ - Listen/bind are not parsed from source; match the sample or the probe fails
440
+ at deploy time. Check Process Log / \`gf coderunner logs -c <id>\`.
441
+ `.trim();
442
+ var CODERUNNER_RUNTIME_CONTRACT = `
443
+ # Runtime contract
444
+
445
+ ${CODERUNNER_RUNTIME_BODY}
446
+ `.trim();
447
+
383
448
  // src/coderunner-guide.ts
384
449
  var CODERUNNER_GUIDE = `
385
450
  # Coderunner workloads
@@ -391,6 +456,12 @@ The Customer Portal, \`gf\` CLI, and this MCP all drive the same lifecycle.
391
456
  Do **not** create an App to run code. \`POST \u2026/apps\` with \`app_kind=coderunner\`
392
457
  is rejected. Optional Apps are Shell / product packaging only.
393
458
 
459
+ ## Runtime contract
460
+
461
+ ${CODERUNNER_RUNTIME_BODY}
462
+
463
+ Also \`docs_get\` id \`coderunner-runtime\`.
464
+
394
465
  ## Prerequisites (do these before create/deploy)
395
466
 
396
467
  1. **Workspace Authentication is required** before creating a coderunner.
@@ -421,7 +492,7 @@ Pick **one** type per coderunner. Changing type later means a new coderunner.
421
492
  ### Function
422
493
 
423
494
  - ZIP at archive root (\`package.json\` / \`index.js\` or \`main.py\` not nested).
424
- - Listen on \`process.env.PORT || 8080\` (health GET \`/\` or \`/health\`).
495
+ - Listen on \`process.env.PORT || 8080\`, bind **\`0.0.0.0\`** (health GET \`/\` or \`/health\`).
425
496
  - **Node:** \`package.json\` **must** include \`"scripts": { "start": "node index.js" }\`.
426
497
  The runtime image is \`CMD ["npm", "start"]\`. Missing \`start\` \u2192 crash loop,
427
498
  probe \`127.0.0.1:8080\` connection refused, deployment **Failed (status 4)**.
@@ -467,8 +538,9 @@ CMD ["npm", "start"]
467
538
  coderunner if the slug is new; otherwise uploads a new version and deploys it.
468
539
  - Status: upload/build \`1\u20134\` (3=Completed, 4=Failed). Deployment \`1\u20137\`
469
540
  (3=Deployed, 4=Failed). A templated \`deployment_url\` with empty
470
- \`externalEndpoint\` and status 4 means the container never became ready \u2014
471
- read Process Log (\`runner_id\` = deployment id).
541
+ \`externalEndpoint\` and status 4 means the container never became ready.
542
+ \`gf deploy\` prints the last Process Log lines, ids, and a Portal link on
543
+ failure. Fetch more with \`gf coderunner logs -c <id|slug>\`.
472
544
  - Strip \`.env*\`, \`.git\`, \`node_modules\` from ZIPs. Put secrets in
473
545
  \`gf secrets\` / deploy \`-e\`, never in the archive.
474
546
  - Zip **contents** at root, not a wrapping folder.
@@ -513,7 +585,7 @@ When the handler looks right locally, \`gf deploy\` the same tree.
513
585
 
514
586
  ## Agent actions
515
587
 
516
- Inspect: \`workspace_context\`, \`docs_get\` id \`coderunner\`, \`suggest_cli\`.
588
+ Inspect: \`workspace_context\`, \`docs_get\` id \`coderunner\` or \`coderunner-runtime\`, \`suggest_cli\`.
517
589
  Act: \`gf deploy\`, \`gf coderunner run|status\`. MCP does not deploy.
518
590
  `.trim();
519
591
 
@@ -570,9 +642,16 @@ var DOC_CATALOG = [
570
642
  id: "coderunner",
571
643
  title: "Coderunner",
572
644
  path: "/docs/customer-portal/coderunner",
573
- summary: "Workload types (function, job, schedule, service/Deployment), Dockerfile for services, Authentication before create, local live data, gf deploy.",
645
+ summary: "Workload types (function, job, schedule, service/Deployment), runtime contract (scripts.start, 0.0.0.0:8080), Dockerfile for services, Authentication before create, local live data, gf deploy.",
574
646
  embedded: CODERUNNER_GUIDE
575
647
  },
648
+ {
649
+ id: "coderunner-runtime",
650
+ title: "Coderunner runtime contract",
651
+ path: "/docs/customer-portal/coderunner",
652
+ summary: "What the platform expects your code to do: Node bootstrap (npm start, PORT/8080, bind 0.0.0.0), Python bind, service Dockerfile.",
653
+ embedded: CODERUNNER_RUNTIME_CONTRACT
654
+ },
576
655
  {
577
656
  id: "coderunner-overview",
578
657
  title: "Coderunner overview",
@@ -584,7 +663,7 @@ var DOC_CATALOG = [
584
663
  id: "authentication",
585
664
  title: "Workspace Authentication",
586
665
  path: "/docs/customer-portal/authentication",
587
- summary: "Workspace site auth (Groundfloor or external) must be active before creating a Coderunner. Separate from portal Members & Roles."
666
+ summary: "Workspace site auth (Groundfloor or external) must be active before creating a Coderunner. Separate from portal Team."
588
667
  },
589
668
  {
590
669
  id: "api-authentication",
@@ -668,7 +747,7 @@ function suggestCli(intent, args) {
668
747
  case "deploy":
669
748
  return {
670
749
  command: name ? `gf deploy -n ${name}${ws}` : `gf deploy${ws}`,
671
- 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 needs package.json scripts.start. Do not create an App for this.",
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.",
672
751
  docs: "https://docs.groundfloor.cloud/docs/customer-portal/coderunner"
673
752
  };
674
753
  case "run":
@@ -683,6 +762,12 @@ function suggestCli(intent, args) {
683
762
  why: "Inspect build/deploy state.",
684
763
  docs: "https://docs.groundfloor.cloud/docs/customer-portal/coderunner"
685
764
  };
765
+ case "logs":
766
+ return {
767
+ command: name ? `gf coderunner logs -c ${name}${ws}` : `gf coderunner logs -c <id-or-slug>${ws}`,
768
+ why: "Process Log (stdout/stderr). gf deploy prints the last lines on failure; this command fetches more. runner_id accepts a coderunner id.",
769
+ docs: "https://docs.groundfloor.cloud/docs/customer-portal/coderunner"
770
+ };
686
771
  case "init_app": {
687
772
  const slug = args?.slug?.trim();
688
773
  return {
@@ -756,7 +841,7 @@ async function resolveCoderunnerId(apiUrl, workspaceId, idOrSlug) {
756
841
  }
757
842
  var server = new McpServer({
758
843
  name: "groundfloor",
759
- version: "0.1.3"
844
+ version: "0.1.5"
760
845
  });
761
846
  server.tool(
762
847
  "whoami",
@@ -783,7 +868,7 @@ server.tool(
783
868
  );
784
869
  server.tool(
785
870
  "workspace_context",
786
- "Live context for the selected workspace: session, coderunners, apps, dataplane, suggested gf commands, and Coderunner type/auth/Dockerfile rules. Use docs_get id coderunner for the full how-to.",
871
+ "Live context for the selected workspace: session, coderunners, apps, dataplane, suggested gf commands, and Coderunner type/auth/Dockerfile/runtime-contract rules. Use docs_get id coderunner (or coderunner-runtime) before deploying.",
787
872
  { workspace_id: wsId },
788
873
  async ({ workspace_id }) => {
789
874
  try {
@@ -816,14 +901,15 @@ server.tool(
816
901
  dataplane,
817
902
  coderunner_howto: {
818
903
  docs_get: "coderunner",
904
+ runtime_contract: "coderunner-runtime",
819
905
  before_create: "Enable Administer \u2192 Authentication (mode groundfloor or external, realm active). Mode none cannot mint a Code Runner JWT.",
820
906
  types: {
821
- function: "HTTP handler on PORT/8080. Invoke with gf coderunner run or the Deployment URL. Node requires package.json scripts.start.",
907
+ 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.",
822
908
  job: "Run-to-completion worker. Deploy, then gf coderunner run.",
823
909
  schedule: "Cron job. No public Deployment URL. Set schedule/cron at create or deploy.",
824
- service: "Long-running Deployment (same type as service). Dockerfile at ZIP root is required. Listen on 8080."
910
+ service: "Long-running Deployment (same type as service). Dockerfile at ZIP root is required. Listen on 8080 / 0.0.0.0."
825
911
  },
826
- node: 'Bootstrap image uses CMD ["npm","start"]. Missing scripts.start \u2192 deploy Failed (status 4), 8080 connection refused.',
912
+ 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.",
827
913
  local_live_data: 'eval "$(gf env)" then call Control Plane vault/files/secrets with Bearer $(gf token). Do not zip .env files.'
828
914
  },
829
915
  shell_app_howto: {
@@ -835,6 +921,7 @@ server.tool(
835
921
  deploy: suggestCli("deploy"),
836
922
  switch_workspace: suggestCli("switch_workspace"),
837
923
  run: suggestCli("run"),
924
+ logs: suggestCli("logs"),
838
925
  init_app: suggestCli("init_app"),
839
926
  publish_app: suggestCli("publish_app")
840
927
  },
@@ -912,7 +999,7 @@ server.tool(
912
999
  );
913
1000
  server.tool(
914
1001
  "coderunner_get_logs",
915
- "Fetch logs for a job id from a previous `gf coderunner run`.",
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`).",
916
1003
  {
917
1004
  workspace_id: wsId,
918
1005
  coderunner_id: z.string().min(1),
@@ -1040,10 +1127,10 @@ server.tool(
1040
1127
  );
1041
1128
  server.tool(
1042
1129
  "docs_get",
1043
- "How-to for Coderunner types, Dockerfile, Authentication, local live data, gf CLI, Shell. Pass id from docs_list (coderunner, coderunner-overview, agents, developers, authentication, api-authentication, shell). Prefer id coderunner before deploying.",
1130
+ "How-to for Coderunner types, runtime contract (scripts.start, 0.0.0.0:8080), Dockerfile, Authentication, local live data, gf CLI, Shell. Pass id from docs_list. Prefer id coderunner or coderunner-runtime before deploying.",
1044
1131
  {
1045
1132
  id: z.string().min(1).describe(
1046
- "Catalog id, e.g. coderunner | coderunner-overview | agents | developers | authentication | api-authentication | shell"
1133
+ "Catalog id, e.g. coderunner | coderunner-runtime | coderunner-overview | agents | developers | authentication | api-authentication | shell"
1047
1134
  )
1048
1135
  },
1049
1136
  async ({ id }) => {
@@ -1072,13 +1159,14 @@ server.tool(
1072
1159
  "deploy",
1073
1160
  "run",
1074
1161
  "status",
1162
+ "logs",
1075
1163
  "init_app",
1076
1164
  "publish_app",
1077
1165
  "secrets",
1078
1166
  "dataplane"
1079
1167
  ]),
1080
1168
  workspace: z.string().optional(),
1081
- name: z.string().optional().describe("Coderunner name/id for deploy/run/status"),
1169
+ name: z.string().optional().describe("Coderunner name/id for deploy/run/status/logs"),
1082
1170
  slug: z.string().optional().describe("Portal app slug for init_app / publish_app"),
1083
1171
  cell: z.enum(["production", "stage", "dev"]).optional()
1084
1172
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groundfloorcloud/mcp",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Groundfloor MCP — inspect workspace state and docs; take actions with the gf CLI.",
5
5
  "type": "module",
6
6
  "bin": {