@groundfloorcloud/mcp 0.1.9 → 0.1.12

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 (2) hide show
  1. package/dist/index.js +111 -15
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -473,6 +473,22 @@ server.listen(port, "0.0.0.0", () => {
473
473
 
474
474
  Offer GET \`/\` or \`/health\` for the probe. Sample: \`samples/hello-fn/\`.
475
475
 
476
+ ## Known issue: bootstrap has been reported to skip \`npm start\`
477
+
478
+ Reported 2026-09-22 against a \`function\` deploy with no Dockerfile, **unresolved as of this
479
+ writing**: instead of running \`npm start\` per the contract above, the platform's own
480
+ \`/app/main.js\` ran \`require('./handler')\` \u2014 a file/module contract this document does not
481
+ describe \u2014 even though \`scripts.start\` and the entrypoint were present and correct. Symptom in
482
+ \`gf coderunner logs -c <id>\`: \`Cannot find module './handler'\` (\`MODULE_NOT_FOUND\`), repeated
483
+ probe \`127.0.0.1:8080\` connection refused, deployment **Failed (status 4)**. Whether \`job\` and
484
+ \`schedule\` share this bootstrap path and are equally affected has not been confirmed either way \u2014
485
+ treat the workaround below as precautionary for those types too if you hit the same signature.
486
+
487
+ **Workaround:** add your own root \`Dockerfile\` (e.g. \`CMD ["npm", "start"]\`) and keep your
488
+ existing \`workload_type\` \u2014 a Dockerfile bypasses the injected bootstrap entirely regardless of
489
+ type (see "Node (with Dockerfile)" below), so this sidesteps the bug without changing how the
490
+ workload is invoked.
491
+
476
492
  ## Node (with Dockerfile)
477
493
 
478
494
  You own \`CMD\`. If it is \`npm start\`, you still need \`scripts.start\`.
@@ -556,11 +572,17 @@ Pick **one** type per coderunner. Changing type later means a new coderunner.
556
572
  probe \`127.0.0.1:8080\` connection refused, deployment **Failed (status 4)**.
557
573
  - Python: process must bind 0.0.0.0:\${PORT:-8080}.
558
574
  - After deploy: \`gf coderunner run -c <slug> --payload '{"hello":"world"}'\`.
575
+ - \u26A0\uFE0F See "Known issue" in \`docs_get\` id \`coderunner-runtime\` if \`gf coderunner logs\` shows
576
+ \`Cannot find module './handler'\` despite a correct \`scripts.start\` \u2014 add a Dockerfile as the
577
+ workaround, no type change needed.
559
578
 
560
579
  ### Job
561
580
 
562
581
  - Same package rules as function. No need to serve forever; exit 0 on success.
563
582
  - Deploy, then start a run (\`gf coderunner run\`). Check Process Log.
583
+ - \u26A0\uFE0F Not confirmed whether the no-Dockerfile bootstrap bug in \`docs_get\` id \`coderunner-runtime\`
584
+ ("Known issue") also hits \`job\` \u2014 if a run never starts and logs show the same
585
+ \`MODULE_NOT_FOUND: './handler'\`, apply the same Dockerfile workaround.
564
586
 
565
587
  ### Schedule
566
588
 
@@ -570,6 +592,8 @@ Pick **one** type per coderunner. Changing type later means a new coderunner.
570
592
  - GET/list/status expose top-level \`workload_type\` and \`schedule\`.
571
593
  \`gf coderunner status -c <slug>\` prints type, schedule, last_job.
572
594
  - No public invoke URL. Debug with \`gf coderunner logs\`, not curl.
595
+ - \u26A0\uFE0F Same caveat as Job: unconfirmed whether the bootstrap "Known issue" affects scheduled runs;
596
+ same Dockerfile workaround if you see the identical crash signature.
573
597
 
574
598
  ### Service (Deployment)
575
599
 
@@ -661,8 +685,13 @@ When the handler looks right locally, \`gf deploy\` the same tree.
661
685
  Do not pass credential-shaped keys as \`--env KEY=VALUE\` (\`*_SECRET\`,
662
686
  \`*_API_KEY\`, \`DATABASE_URL\`, \u2026). Control Plane returns **422**.
663
687
  \`gf secrets set KEY\` then \`gf deploy --secret KEY\` (inject by reference).
688
+ This requires \`CODERUNNER_INJECT_ENV_SECRETS\` enabled for the workspace \u2014
689
+ **off by default**. If \`--secret\` 422s naming that setting, either ask your
690
+ platform operator to enable it, or read the secret live via M2M
691
+ \`secrets:read\` instead (below) rather than falling back to plaintext
692
+ \`--env\`.
664
693
 
665
- ## M2M tokens (Vault and Files)
694
+ ## M2M tokens (Vault, Files, and Secrets)
666
695
 
667
696
  New Coderunners cannot call Vault or Files until an M2M client is attached
668
697
  and the workload is redeployed. Create the client in Portal **Administer \u2192
@@ -670,6 +699,34 @@ M2M tokens**, attach it on the Coderunner card, then deploy. v1 M2M is Team
670
699
  Writer on that workspace (full Vault skip-ACL + Files). Grant site users on
671
700
  **Administer \u2192 Access**. Never inject a Dataplane key.
672
701
 
702
+ Cache the minted JWT for its \`expires_in\` (refresh at ~80%). Never mint per
703
+ request: \`POST \u2026/m2m/token\` is rate-limited per client and per IP (default
704
+ 20/minute each) and returns **429** when exceeded. Treat 429/401 from the mint
705
+ call as retryable, not a crash.
706
+
707
+ An M2M client can also read secret **values** directly at runtime instead
708
+ of (or alongside) \`--secret\` deploy-time injection: grant it the
709
+ **\`secrets:read\`** scope (same Portal **Administer \u2192 M2M tokens** screen,
710
+ per-client toggle), then call
711
+ \`GET {CONTROLPLANE_URL}/v1/workspaces/{workspace_id}/secrets/{key}\` with
712
+ \`Authorization: Bearer\` from \`gf m2m token\` locally, or the deployed
713
+ workload's injected \`GROUNDFLOOR_M2M_CLIENT_ID\`/\`GROUNDFLOOR_M2M_CLIENT_SECRET\`
714
+ exchanged the same way. This path is:
715
+
716
+ - **Read-only** \u2014 \`secrets:read\` never grants \`upsert\`/\`delete\`, regardless
717
+ of the client's writer relation on Vault/Files.
718
+ - **Live** \u2014 a rotated secret is visible on your next call, no redeploy
719
+ needed. \`--secret\` injection only updates on the next deploy.
720
+ - **Workspace-wide, not per-key or per-environment** \u2014 granting the scope
721
+ exposes every secret in the workspace across all environments to that
722
+ client's code, same class of trade as the existing Vault/Files writer
723
+ grant. Not a fit for isolating one secret to one Coderunner.
724
+
725
+ Prefer it over \`--secret\` injection when you want live rotation and can
726
+ do the fetch yourself; keep using \`--secret\` injection for SDKs that only
727
+ read a literal \`process.env\` value. Either way, cache the revealed value
728
+ in your process rather than calling \`reveal\` on every request.
729
+
673
730
  ## Agent actions
674
731
 
675
732
  Inspect: \`workspace_context\`, \`docs_get\` id \`coderunner\` or \`coderunner-runtime\`, \`suggest_cli\`.
@@ -848,6 +905,14 @@ database only if you need a different engine or workspace-owned isolation
848
905
  (\`docs_get\` id \`databases\`). Local UI without live vault:
849
906
  \`npm run dev:fixtures\`.
850
907
 
908
+ Detail routes (e.g. \`records/:id\`): once the record loads, call
909
+ \`setBreadcrumbLabel(record.name)\` from the kit's \`src/lib/shellBridge.ts\`
910
+ (wraps \`window.__GF_SET_BREADCRUMB_LABEL__(location.pathname, label)\`; no-op
911
+ outside Shell) so the breadcrumb does not show the raw id. Give that route
912
+ \`hidden: true\` in the manifest so it stays out of the sidebar nav but still
913
+ routes (omit it or use \`false\` otherwise \u2014 never \`null\`). \`visibility:
914
+ "public"\` makes a route reachable without login.
915
+
851
916
  ## suggest_cli
852
917
 
853
918
  - init_app \u2192 \`gf apps init --slug <portal-slug>\`
@@ -872,6 +937,10 @@ Pass \`engine\` explicitly \u2014 don't rely on Dataplane's default connector.
872
937
  \`"postgresql"\` is the platform default (shared, no deploy step);
873
938
  \`"arangodb"\` is also available the same way.
874
939
 
940
+ \`id\` and \`tenant_id\` are reserved field names on Postgres (and any SQL-shaped
941
+ engine) \u2014 Dataplane adds them automatically as system columns on every table.
942
+ Declaring either as a property is a **400**, not a 500. Not reserved on ArangoDB.
943
+
875
944
  Example:
876
945
 
877
946
  \`\`\`json
@@ -934,6 +1003,18 @@ Do not \`--env SENDGRID_SECRET=\u2026\`. That is **422**.
934
1003
  \`gf secrets set SENDGRID_SECRET\` then \`gf deploy --secret SENDGRID_SECRET\`.
935
1004
  `.trim();
936
1005
 
1006
+ // src/version.ts
1007
+ import { readFileSync } from "fs";
1008
+ import { dirname, join } from "path";
1009
+ import { fileURLToPath } from "url";
1010
+ var moduleDir = dirname(fileURLToPath(import.meta.url));
1011
+ function readPackageVersion() {
1012
+ const pkgPath = join(moduleDir, "..", "package.json");
1013
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1014
+ return pkg.version;
1015
+ }
1016
+ var GF_MCP_VERSION = readPackageVersion();
1017
+
937
1018
  // src/docs.ts
938
1019
  var DOCS_ORIGIN = "https://docs.groundfloor.cloud";
939
1020
  var DOC_CATALOG = [
@@ -954,21 +1035,24 @@ var DOC_CATALOG = [
954
1035
  title: "Coderunner",
955
1036
  path: "/docs/customer-portal/coderunner",
956
1037
  summary: "Workload types (function, job, schedule, service/Deployment), runtime contract (scripts.start, 0.0.0.0:8080), Dockerfile for services, Coderunner identities for Vault, App authentication optional, local live data, gf deploy.",
957
- embedded: CODERUNNER_GUIDE
1038
+ embedded: CODERUNNER_GUIDE,
1039
+ lastUpdated: "2026-09-24"
958
1040
  },
959
1041
  {
960
1042
  id: "coderunner-runtime",
961
1043
  title: "Coderunner runtime contract",
962
1044
  path: "/docs/customer-portal/coderunner",
963
1045
  summary: "What the platform expects your code to do: Node bootstrap (npm start, PORT/8080, bind 0.0.0.0), Python bind, service Dockerfile.",
964
- embedded: CODERUNNER_RUNTIME_CONTRACT
1046
+ embedded: CODERUNNER_RUNTIME_CONTRACT,
1047
+ lastUpdated: "2026-09-23"
965
1048
  },
966
1049
  {
967
1050
  id: "coderunner-overview",
968
1051
  title: "Coderunner overview",
969
1052
  path: "/docs/integrations/coderunner/overview",
970
1053
  summary: "Architecture and lifecycle. Prefer docs_get id coderunner for type usage, Dockerfile, and create prerequisites.",
971
- embedded: CODERUNNER_GUIDE
1054
+ embedded: CODERUNNER_GUIDE,
1055
+ lastUpdated: "2026-09-24"
972
1056
  },
973
1057
  {
974
1058
  id: "authentication",
@@ -986,7 +1070,7 @@ var DOC_CATALOG = [
986
1070
  id: "m2m",
987
1071
  title: "M2M tokens",
988
1072
  path: "/docs/customer-portal/m2m-tokens",
989
- summary: "Confidential client secret \u2192 short Control Plane JWT (typ=m2m). Attach on Coderunner or gf m2m token. Never DATAPLANE_SERVICE_API_KEY. v1 is Team Writer, no collection ACL."
1073
+ summary: "Confidential client secret \u2192 short Control Plane JWT (typ=m2m). Attach on Coderunner or gf m2m token. Never DATAPLANE_SERVICE_API_KEY. v1 is Team Writer, no collection ACL. Secrets only with the opt-in secrets:read scope (read-only). Cache the token; minting is rate-limited per client and IP."
990
1074
  },
991
1075
  {
992
1076
  id: "api-authentication",
@@ -999,42 +1083,48 @@ var DOC_CATALOG = [
999
1083
  title: "Shell federated apps",
1000
1084
  path: "/docs/integrations/shell",
1001
1085
  summary: "Product Apps vs Coderunner. Always gf apps init --slug <portal-slug>, then npm run release and gf apps publish --path release.zip. Never zip dist/ or upload only remoteEntry.js.",
1002
- embedded: SHELL_APP_GUIDE
1086
+ embedded: SHELL_APP_GUIDE,
1087
+ lastUpdated: "2026-09-24"
1003
1088
  },
1004
1089
  {
1005
1090
  id: "design-standard",
1006
1091
  title: "Shell app design standard",
1007
1092
  path: "/docs/integrations/shell/design-standard",
1008
1093
  summary: "Visual rules for Shell apps: grey ground, flat controls, Geist, one brand accent from theme.primaryColor, DataGrid, PhaseChevron. Use the kit; do not restyle to stock Tailwind.",
1009
- embedded: DESIGN_STANDARD_GUIDE
1094
+ embedded: DESIGN_STANDARD_GUIDE,
1095
+ lastUpdated: "2026-09-04"
1010
1096
  },
1011
1097
  {
1012
1098
  id: "css-and-styling",
1013
1099
  title: "CSS and styling",
1014
1100
  path: "/docs/integrations/shell/css-and-styling",
1015
1101
  summary: "Tailwind is the default. Own CSS framework (Bootstrap, MUI, \u2026) uses FederatedStyleRoot and Shadow DOM \u2014 do not load it globally onto the Shell page.",
1016
- embedded: CSS_AND_STYLING_GUIDE
1102
+ embedded: CSS_AND_STYLING_GUIDE,
1103
+ lastUpdated: "2026-09-04"
1017
1104
  },
1018
1105
  {
1019
1106
  id: "databases",
1020
1107
  title: "Databases",
1021
1108
  path: "/docs/customer-portal/databases",
1022
1109
  summary: "Dataplane is not a database. Postgres (shared, platform-managed) is the default, ready through Dataplane with no deploy step. ArangoDB is also ready the same way. A dedicated database you deploy and own is a separate, heavier path.",
1023
- embedded: DATABASES_GUIDE
1110
+ embedded: DATABASES_GUIDE,
1111
+ lastUpdated: "2026-09-22"
1024
1112
  },
1025
1113
  {
1026
1114
  id: "vault",
1027
1115
  title: "Data Vault",
1028
1116
  path: "/docs/api/vault",
1029
1117
  summary: "Create collections with JSON Schema, query totals vs limit, ACLs, implicit tables, nested filters, identities vs M2M.",
1030
- embedded: VAULT_GUIDE
1118
+ embedded: VAULT_GUIDE,
1119
+ lastUpdated: "2026-09-22"
1031
1120
  },
1032
1121
  {
1033
1122
  id: "scheduled-jobs",
1034
1123
  title: "Scheduled jobs",
1035
1124
  path: "/docs/customer-portal/scheduled-jobs",
1036
1125
  summary: "workload_type=schedule plus cron. gf coderunner status shows type/schedule/last_job. Use Process Log, not a public URL.",
1037
- embedded: CODERUNNER_GUIDE
1126
+ embedded: CODERUNNER_GUIDE,
1127
+ lastUpdated: "2026-09-24"
1038
1128
  }
1039
1129
  ];
1040
1130
  function docUrl(entry) {
@@ -1061,7 +1151,9 @@ async function fetchDocPage(entry) {
1061
1151
  title: entry.title,
1062
1152
  text: entry.embedded.trim(),
1063
1153
  truncated: false,
1064
- source: "embedded"
1154
+ source: "embedded",
1155
+ lastUpdated: entry.lastUpdated ?? null,
1156
+ mcpVersion: GF_MCP_VERSION
1065
1157
  };
1066
1158
  }
1067
1159
  const res = await fetch(url, {
@@ -1081,7 +1173,9 @@ async function fetchDocPage(entry) {
1081
1173
  title: entry.title,
1082
1174
  text: body,
1083
1175
  truncated,
1084
- source: "docs.groundfloor.cloud"
1176
+ source: "docs.groundfloor.cloud",
1177
+ lastUpdated: null,
1178
+ mcpVersion: GF_MCP_VERSION
1085
1179
  };
1086
1180
  }
1087
1181
 
@@ -1218,7 +1312,7 @@ async function resolveCoderunnerId(apiUrl, workspaceId, idOrSlug) {
1218
1312
  }
1219
1313
  var server = new McpServer({
1220
1314
  name: "groundfloor",
1221
- version: "0.1.7"
1315
+ version: GF_MCP_VERSION
1222
1316
  });
1223
1317
  server.tool(
1224
1318
  "whoami",
@@ -1599,11 +1693,13 @@ server.tool(
1599
1693
  const hits = findDoc(query ?? "");
1600
1694
  return text({
1601
1695
  origin: "https://docs.groundfloor.cloud",
1696
+ mcpVersion: GF_MCP_VERSION,
1602
1697
  docs: hits.map((d) => ({
1603
1698
  id: d.id,
1604
1699
  title: d.title,
1605
1700
  url: `https://docs.groundfloor.cloud${d.path}`,
1606
- summary: d.summary
1701
+ summary: d.summary,
1702
+ lastUpdated: d.lastUpdated ?? null
1607
1703
  }))
1608
1704
  });
1609
1705
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groundfloorcloud/mcp",
3
- "version": "0.1.9",
3
+ "version": "0.1.12",
4
4
  "description": "Groundfloor MCP — inspect workspace state and docs; take actions with the gf CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,6 +38,7 @@
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^20",
41
+ "esbuild": "^0.27.7",
41
42
  "tsup": "^8.3.0",
42
43
  "typescript": "^5"
43
44
  },