@groundfloorcloud/mcp 0.1.0 → 0.1.2

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 +38 -3
  2. package/dist/index.js +212 -17
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -22,6 +22,8 @@ Switch workspace: `gf workspaces use <uuid>` (then MCP sees it). Expired session
22
22
 
23
23
  `gf` must be on `PATH` and you must have run `gf login` (and usually `gf workspaces use`).
24
24
 
25
+ Clients that spawn a real Node (Claude Code, some IDEs) can use:
26
+
25
27
  ```json
26
28
  {
27
29
  "mcpServers": {
@@ -33,7 +35,28 @@ Switch workspace: `gf workspaces use <uuid>` (then MCP sees it). Expired session
33
35
  }
34
36
  ```
35
37
 
36
- Optional: `"GROUNDFLOOR_API_URL"` only if you are not using the cell from `gf login`. Do not put a JWT in mcp.json.
38
+ **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
+
40
+ ```bash
41
+ npm i -g @groundfloorcloud/mcp
42
+ which node
43
+ # → /Users/you/.nvm/versions/node/v20.x.x/bin/node
44
+ ```
45
+
46
+ ```json
47
+ {
48
+ "mcpServers": {
49
+ "groundfloor": {
50
+ "command": "/FULL/PATH/TO/node",
51
+ "args": [
52
+ "/FULL/PATH/TO/lib/node_modules/@groundfloorcloud/mcp/dist/index.js"
53
+ ]
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ The `command` must be your Node (from `which node`), not `npx` and not Cursor’s helper Node. Do not put a JWT in mcp.json.
37
60
 
38
61
  ## Tools
39
62
 
@@ -44,11 +67,23 @@ Optional: `"GROUNDFLOOR_API_URL"` only if you are not using the cell from `gf lo
44
67
  | `coderunner_list` / `_get_status` / `_get_logs` | Inspect workloads |
45
68
  | `apps_list` / `apps_get` | Inspect product Apps |
46
69
  | `secrets_list` / `files_list` / `domains_list` / `dataplane_status` | Inspect (no secrets values) |
47
- | `docs_list` / `docs_get` | Product docs |
48
- | `suggest_cli` | Command to run: login, deploy, run, … |
70
+ | `docs_list` / `docs_get` | Product docs (`coderunner` returns the full type/auth/Dockerfile guide) |
71
+ | `suggest_cli` | Command to run: login, deploy, init_app, publish_app, … |
49
72
 
50
73
  Never create `app_kind=coderunner`. Deploy with `gf deploy`.
51
74
 
75
+ **Coderunner how-to** is embedded: `docs_get` id `coderunner` (also
76
+ `coderunner-overview`). That page covers:
77
+
78
+ - Types: `function`, `job`, `schedule`, `service` (**also called Deployment**)
79
+ - **Authentication must be active** before creating a coderunner
80
+ - **Dockerfile is required** for `service` / Deployment
81
+ - Node `scripts.start` + listen on 8080
82
+ - Local development against live vault/files/secrets (`gf env` + `gf token`)
83
+
84
+ `workspace_context` includes a `coderunner_howto` summary so agents see this
85
+ without an extra fetch.
86
+
52
87
  ## Build (from this repo)
53
88
 
54
89
  ```bash
package/dist/index.js CHANGED
@@ -296,17 +296,20 @@ var CELLS = {
296
296
  production: {
297
297
  apiUrl: "https://platform.groundfloor.cloud",
298
298
  issuer: "https://auth.groundfloor.cloud/realms/groundfloor",
299
- clientId: "groundfloor-cli"
299
+ clientId: "groundfloor-cli",
300
+ consoleUrl: "https://console.groundfloor.cloud"
300
301
  },
301
302
  stage: {
302
303
  apiUrl: "https://platform.stage.groundfloor.cloud",
303
304
  issuer: "https://auth.stage.groundfloor.cloud/realms/groundfloor_pico_stage-realm",
304
- clientId: "groundfloor-cli"
305
+ clientId: "groundfloor-cli",
306
+ consoleUrl: "https://console.stage.groundfloor.cloud"
305
307
  },
306
308
  dev: {
307
309
  apiUrl: "https://platform.dev.groundfloor.cloud",
308
310
  issuer: "https://auth.dev.groundfloor.cloud/realms/groundfloor_dev",
309
- clientId: "groundfloor-cli"
311
+ clientId: "groundfloor-cli",
312
+ consoleUrl: "https://console.dev.groundfloor.cloud"
310
313
  }
311
314
  };
312
315
  function inferCell(apiUrl, issuer) {
@@ -377,6 +380,143 @@ async function resolveMcpConfig(overrides) {
377
380
  return { apiUrl, workspaceId, token };
378
381
  }
379
382
 
383
+ // src/coderunner-guide.ts
384
+ var CODERUNNER_GUIDE = `
385
+ # Coderunner workloads
386
+
387
+ Coderunner is Groundfloor's **apps runtime**. Deploy Node.js, Python, .NET, and
388
+ other workloads as **functions**, **jobs**, **schedules**, or **services**.
389
+ The Customer Portal, \`gf\` CLI, and this MCP all drive the same lifecycle.
390
+
391
+ Do **not** create an App to run code. \`POST \u2026/apps\` with \`app_kind=coderunner\`
392
+ is rejected. Optional Apps are Shell / product packaging only.
393
+
394
+ ## Prerequisites (do these before create/deploy)
395
+
396
+ 1. **Workspace Authentication is required** before creating a coderunner.
397
+ Portal \u2192 Administer \u2192 Authentication \u2192 set mode to \`groundfloor\` (or
398
+ \`external\` with an issuer) and wait until the realm is **active**.
399
+ Code Runner JWTs use the workspace site issuer. Mode \`none\` cannot mint
400
+ that token \u2014 create/deploy fails with "requires the workspace site issuer".
401
+ 2. \`gf login\` (\`--stage\` / \`--dev\` as needed) and \`gf workspaces use <uuid>\`.
402
+ 3. Dataplane for vault/files/secrets: \`gf dataplane provision\` if those APIs
403
+ are needed (local live data or the deployed workload).
404
+
405
+ ## Workload types
406
+
407
+ Portal \`workload_type\` (CLI \`--workload-type\` / \`groundfloor.json\`
408
+ \`workloadType\`) maps to upstream \`deploymentType\`. **Service and Deployment
409
+ are the same type** \u2014 portal id \`service\`, UI often says Deployment,
410
+ upstream \`deploymentType=4\`.
411
+
412
+ | Portal id | Also called | Upstream | When to use | How it runs |
413
+ |-----------|-------------|----------|-------------|-------------|
414
+ | \`function\` | Function | 1 | Request/response HTTP handler (webhooks, APIs, agent invoke) | Scale-to-zero Knative. Invoke via Run or Deployment URL. |
415
+ | \`job\` | Background job | 2 | One-shot batch (import, report, backfill) | Run-to-completion. Deploy, then \`gf coderunner run\`. |
416
+ | \`schedule\` | Scheduled job | 3 | Cron (reminders, sync) | Same as job, plus cron. **No** public Deployment URL. |
417
+ | \`service\` | **Deployment** | 4 | Long-running HTTP process (web app, always-on API) | Stays up (replicas). Public Deployment URL. Custom domains when App-bound. |
418
+
419
+ Pick **one** type per coderunner. Changing type later means a new coderunner.
420
+
421
+ ### Function
422
+
423
+ - ZIP at archive root (\`package.json\` / \`index.js\` or \`main.py\` not nested).
424
+ - Listen on \`process.env.PORT || 8080\` (health GET \`/\` or \`/health\`).
425
+ - **Node:** \`package.json\` **must** include \`"scripts": { "start": "node index.js" }\`.
426
+ The runtime image is \`CMD ["npm", "start"]\`. Missing \`start\` \u2192 crash loop,
427
+ probe \`127.0.0.1:8080\` connection refused, deployment **Failed (status 4)**.
428
+ - Python: process must bind 0.0.0.0:\${PORT:-8080}.
429
+ - After deploy: \`gf coderunner run -c <slug> --payload '{"hello":"world"}'\`.
430
+
431
+ ### Job
432
+
433
+ - Same package rules as function. No need to serve forever; exit 0 on success.
434
+ - Deploy, then start a run (\`gf coderunner run\`). Check Process Log.
435
+
436
+ ### Schedule
437
+
438
+ - Same as job, plus a cron expression at create/deploy (\`schedule\` /
439
+ \`CronExpression\`).
440
+ - No public invoke URL. Debug with Process Log, not curl.
441
+
442
+ ### Service (Deployment)
443
+
444
+ - Long-running HTTP. **You must include a \`Dockerfile\` at the ZIP root.**
445
+ Kaniko builds with \`--dockerfile=Dockerfile\`. Language bootstrap does **not**
446
+ replace a missing Dockerfile for this type.
447
+ - Dockerfile must expose and listen on **8080** (or \`PORT\`).
448
+ - Node example:
449
+
450
+ \`\`\`dockerfile
451
+ FROM node:18-alpine
452
+ WORKDIR /app
453
+ COPY package*.json ./
454
+ RUN npm ci --omit=dev
455
+ COPY . .
456
+ EXPOSE 8080
457
+ ENV PORT=8080
458
+ CMD ["npm", "start"]
459
+ \`\`\`
460
+
461
+ - Still require Node \`"start"\` if CMD is \`npm start\`.
462
+ - Prefer curling the Deployment URL (\`/health\`) over \`\u2026/run\`.
463
+
464
+ ## Latest deploy rules (all types)
465
+
466
+ - \`gf deploy\` from the project dir (or \`--path\` / \`--git\`). Creates the
467
+ coderunner if the slug is new; otherwise uploads a new version and deploys it.
468
+ - Status: upload/build \`1\u20134\` (3=Completed, 4=Failed). Deployment \`1\u20137\`
469
+ (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).
472
+ - Strip \`.env*\`, \`.git\`, \`node_modules\` from ZIPs. Put secrets in
473
+ \`gf secrets\` / deploy \`-e\`, never in the archive.
474
+ - Zip **contents** at root, not a wrapping folder.
475
+ - Optional \`groundfloor.json\`: \`name\`, \`runtime\`, \`workloadType\`,
476
+ \`cpu\`, \`memory\`, \`env\`.
477
+
478
+ ## Local development with live workspace data
479
+
480
+ Run the process on your laptop; call the **same** Control Plane + Dataplane
481
+ the deployed coderunner uses. Do not copy production data into fixtures.
482
+
483
+ \`\`\`bash
484
+ gf login --stage # or --dev / production
485
+ gf workspaces use <uuid>
486
+ eval "$(gf env)" # GROUNDFLOOR_TOKEN, CONTROLPLANE_URL, GROUNDFLOOR_WORKSPACE_ID
487
+ gf dataplane status # provision if needed: gf dataplane provision
488
+ gf secrets ls
489
+ \`\`\`
490
+
491
+ Local HTTP (Node function/service):
492
+
493
+ \`\`\`bash
494
+ export PORT=8080
495
+ npm start # same entrypoint as production
496
+ # other terminal:
497
+ curl -sS -X POST http://127.0.0.1:8080/ -H 'Content-Type: application/json' \\
498
+ -d '{"hello":"local"}'
499
+ \`\`\`
500
+
501
+ Live vault / files / secrets from the laptop:
502
+
503
+ \`\`\`bash
504
+ curl -sS -H "Authorization: Bearer $(gf token)" \\
505
+ "$CONTROLPLANE_URL/v1/workspaces/$GROUNDFLOOR_WORKSPACE_ID/vault/collections"
506
+ \`\`\`
507
+
508
+ In app code, use \`CONTROLPLANE_URL\` + Bearer (\`gf token\` locally;
509
+ injected env / workspace secrets after \`gf deploy\`). Never put
510
+ \`DATAPLANE_SERVICE_API_KEY\` in a browser bundle.
511
+
512
+ When the handler looks right locally, \`gf deploy\` the same tree.
513
+
514
+ ## Agent actions
515
+
516
+ Inspect: \`workspace_context\`, \`docs_get\` id \`coderunner\`, \`suggest_cli\`.
517
+ Act: \`gf deploy\`, \`gf coderunner run|status\`. MCP does not deploy.
518
+ `.trim();
519
+
380
520
  // src/docs.ts
381
521
  var DOCS_ORIGIN = "https://docs.groundfloor.cloud";
382
522
  var DOC_CATALOG = [
@@ -384,22 +524,36 @@ var DOC_CATALOG = [
384
524
  id: "agents",
385
525
  title: "Deploy with agents",
386
526
  path: "/docs/customer-portal/agents",
387
- summary: "How agents should deploy Coderunner workloads with gf / MCP."
527
+ summary: "How agents should deploy Coderunner workloads and init/publish Shell apps with gf / MCP. Enable workspace Authentication before create. Service (Deployment) requires a Dockerfile."
388
528
  },
389
529
  {
390
530
  id: "developers",
391
531
  title: "Developers (gf CLI)",
392
532
  path: "/docs/customer-portal/developers",
393
- summary: "gf login, workspaces, deploy, secrets, files, apps."
533
+ summary: "gf login, workspaces, deploy, secrets, files, apps. Coderunner types: function, job, schedule, service (also called Deployment)."
394
534
  },
395
535
  {
396
536
  id: "coderunner",
397
537
  title: "Coderunner",
398
538
  path: "/docs/customer-portal/coderunner",
399
- summary: "Functions, jobs, schedules, services \u2014 not Apps."
539
+ summary: "Workload types (function, job, schedule, service/Deployment), Dockerfile for services, Authentication before create, local live data, gf deploy.",
540
+ embedded: CODERUNNER_GUIDE
541
+ },
542
+ {
543
+ id: "coderunner-overview",
544
+ title: "Coderunner overview",
545
+ path: "/docs/integrations/coderunner/overview",
546
+ summary: "Architecture and lifecycle. Prefer docs_get id coderunner for type usage, Dockerfile, and auth prerequisites.",
547
+ embedded: CODERUNNER_GUIDE
400
548
  },
401
549
  {
402
550
  id: "authentication",
551
+ title: "Workspace Authentication",
552
+ path: "/docs/customer-portal/authentication",
553
+ summary: "Workspace site auth (Groundfloor or external) must be active before creating a Coderunner. Separate from portal Members & Roles."
554
+ },
555
+ {
556
+ id: "api-authentication",
403
557
  title: "API authentication",
404
558
  path: "/docs/api/authentication",
405
559
  summary: "Control Plane Bearer tokens and workspace identity."
@@ -418,7 +572,7 @@ function findDoc(query) {
418
572
  const q = query.trim().toLowerCase();
419
573
  if (!q) return DOC_CATALOG;
420
574
  return DOC_CATALOG.filter((d) => {
421
- const hay = `${d.id} ${d.title} ${d.summary} ${d.path}`.toLowerCase();
575
+ const hay = `${d.id} ${d.title} ${d.summary} ${d.path} ${d.embedded ?? ""}`.toLowerCase();
422
576
  return q.split(/\s+/).every((part) => hay.includes(part));
423
577
  });
424
578
  }
@@ -429,6 +583,15 @@ function htmlToText(html) {
429
583
  var MAX_CHARS = 12e3;
430
584
  async function fetchDocPage(entry) {
431
585
  const url = docUrl(entry);
586
+ if (entry.embedded?.trim()) {
587
+ return {
588
+ url,
589
+ title: entry.title,
590
+ text: entry.embedded.trim(),
591
+ truncated: false,
592
+ source: "embedded"
593
+ };
594
+ }
432
595
  const res = await fetch(url, {
433
596
  headers: { Accept: "text/html", "User-Agent": "groundfloor-mcp/0.1" },
434
597
  redirect: "follow"
@@ -441,7 +604,13 @@ async function fetchDocPage(entry) {
441
604
  const truncated = body.length > MAX_CHARS;
442
605
  if (truncated) body = `${body.slice(0, MAX_CHARS)}
443
606
  \u2026`;
444
- return { url, title: entry.title, text: body, truncated };
607
+ return {
608
+ url,
609
+ title: entry.title,
610
+ text: body,
611
+ truncated,
612
+ source: "docs.groundfloor.cloud"
613
+ };
445
614
  }
446
615
 
447
616
  // src/playbook.ts
@@ -464,7 +633,7 @@ function suggestCli(intent, args) {
464
633
  case "deploy":
465
634
  return {
466
635
  command: name ? `gf deploy -n ${name}${ws}` : `gf deploy${ws}`,
467
- why: "Package the current (or --path) folder and deploy as a Coderunner. Do not create an App for this.",
636
+ 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.",
468
637
  docs: "https://docs.groundfloor.cloud/docs/customer-portal/coderunner"
469
638
  };
470
639
  case "run":
@@ -479,10 +648,18 @@ function suggestCli(intent, args) {
479
648
  why: "Inspect build/deploy state.",
480
649
  docs: "https://docs.groundfloor.cloud/docs/customer-portal/coderunner"
481
650
  };
651
+ case "init_app": {
652
+ const slug = args?.slug?.trim();
653
+ return {
654
+ command: slug ? `gf apps init --slug ${slug}` : "gf apps init --slug <portal-slug>",
655
+ why: "Download the official Shell starter-kit and stamp APP_ID. Never scaffold a federated App.tsx from memory \u2014 BrowserRouter in App.tsx crashes the Shell.",
656
+ docs: "https://docs.groundfloor.cloud/docs/integrations/shell"
657
+ };
658
+ }
482
659
  case "publish_app":
483
660
  return {
484
- command: "gf apps publish --path release.zip",
485
- why: "Ship a Shell federated product App (not a Coderunner workload).",
661
+ command: args?.slug ? `gf apps init --slug ${args.slug} && cd ${args.slug} && npm install && npm run release && gf apps publish --path release.zip` : "gf apps init --slug <portal-slug> && cd <slug> && npm install && npm run release && gf apps publish --path release.zip",
662
+ why: "Ship a Shell federated product App. Init the starter-kit first; run npm run release (do not zip Vite dist/). Do not gf deploy and do not invent App.tsx.",
486
663
  docs: "https://docs.groundfloor.cloud/docs/integrations/shell"
487
664
  };
488
665
  case "secrets":
@@ -500,7 +677,7 @@ function suggestCli(intent, args) {
500
677
  default:
501
678
  return {
502
679
  command: "gf --help",
503
- why: `Unknown intent "${intent}". Use login | switch_workspace | deploy | run | status | publish_app | secrets | dataplane.`,
680
+ why: `Unknown intent "${intent}". Use login | switch_workspace | deploy | run | status | init_app | publish_app | secrets | dataplane.`,
504
681
  docs: "https://docs.groundfloor.cloud/docs/customer-portal/developers"
505
682
  };
506
683
  }
@@ -571,7 +748,7 @@ server.tool(
571
748
  );
572
749
  server.tool(
573
750
  "workspace_context",
574
- "Live context for the selected workspace: session, coderunners, apps, dataplane, and suggested gf commands. This is how the agent knows *what is here*; use docs_get for *how to do it*.",
751
+ "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.",
575
752
  { workspace_id: wsId },
576
753
  async ({ workspace_id }) => {
577
754
  try {
@@ -602,6 +779,18 @@ server.tool(
602
779
  status: app.status
603
780
  })),
604
781
  dataplane,
782
+ coderunner_howto: {
783
+ docs_get: "coderunner",
784
+ before_create: "Enable Administer \u2192 Authentication (mode groundfloor or external, realm active). Mode none cannot mint a Code Runner JWT.",
785
+ types: {
786
+ function: "HTTP handler on PORT/8080. Invoke with gf coderunner run or the Deployment URL. Node requires package.json scripts.start.",
787
+ job: "Run-to-completion worker. Deploy, then gf coderunner run.",
788
+ schedule: "Cron job. No public Deployment URL. Set schedule/cron at create or deploy.",
789
+ service: "Long-running Deployment (same type as service). Dockerfile at ZIP root is required. Listen on 8080."
790
+ },
791
+ node: 'Bootstrap image uses CMD ["npm","start"]. Missing scripts.start \u2192 deploy Failed (status 4), 8080 connection refused.',
792
+ local_live_data: 'eval "$(gf env)" then call Control Plane vault/files/secrets with Bearer $(gf token). Do not zip .env files.'
793
+ },
605
794
  actions: {
606
795
  deploy: suggestCli("deploy"),
607
796
  switch_workspace: suggestCli("switch_workspace"),
@@ -610,7 +799,8 @@ server.tool(
610
799
  docs: DOC_CATALOG.map((d) => ({
611
800
  id: d.id,
612
801
  title: d.title,
613
- url: `https://docs.groundfloor.cloud${d.path}`
802
+ url: `https://docs.groundfloor.cloud${d.path}`,
803
+ summary: d.summary
614
804
  }))
615
805
  });
616
806
  } catch (e) {
@@ -808,9 +998,11 @@ server.tool(
808
998
  );
809
999
  server.tool(
810
1000
  "docs_get",
811
- "Fetch a product doc page from docs.groundfloor.cloud (how/when to use CLI). Pass id from docs_list (agents, developers, coderunner, authentication, shell).",
1001
+ "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.",
812
1002
  {
813
- id: z.string().min(1).describe("Catalog id, e.g. agents | developers | coderunner")
1003
+ id: z.string().min(1).describe(
1004
+ "Catalog id, e.g. coderunner | coderunner-overview | agents | developers | authentication | api-authentication | shell"
1005
+ )
814
1006
  },
815
1007
  async ({ id }) => {
816
1008
  try {
@@ -838,12 +1030,14 @@ server.tool(
838
1030
  "deploy",
839
1031
  "run",
840
1032
  "status",
1033
+ "init_app",
841
1034
  "publish_app",
842
1035
  "secrets",
843
1036
  "dataplane"
844
1037
  ]),
845
1038
  workspace: z.string().optional(),
846
1039
  name: z.string().optional().describe("Coderunner name/id for deploy/run/status"),
1040
+ slug: z.string().optional().describe("Portal app slug for init_app / publish_app"),
847
1041
  cell: z.enum(["production", "stage", "dev"]).optional()
848
1042
  },
849
1043
  async (args) => {
@@ -852,7 +1046,8 @@ server.tool(
852
1046
  workspace: args.workspace ?? "",
853
1047
  name: args.name ?? "",
854
1048
  coderunner: args.name ?? "",
855
- cell: args.cell ?? ""
1049
+ cell: args.cell ?? "",
1050
+ slug: args.slug ?? ""
856
1051
  })
857
1052
  );
858
1053
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groundfloorcloud/mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Groundfloor MCP — inspect workspace state and docs; take actions with the gf CLI.",
5
5
  "type": "module",
6
6
  "bin": {