@groundfloorcloud/mcp 0.2.2 → 0.2.4

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 +2 -2
  2. package/dist/index.js +244 -30
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -81,8 +81,8 @@ Use the prefixed names: `GROUNDFLOOR_API_URL`, `GROUNDFLOOR_WORKSPACE_ID`, `GROU
81
81
  | `apps_list` / `apps_get` | Inspect product Apps |
82
82
  | `secrets_list` / `files_list` / `dataplane_status` | Inspect (no secrets values) |
83
83
  | `domains_list` | Custom domains of an App's Coderunner (`app_id` or `coderunner_id`). Domains exist on Apps, not standalone Coderunners |
84
- | `quota_get` / `billing_summary` | Plan quota and billing for the workspace's **account** (account id is not the workspace id; billing needs Owner or Billing admin) |
85
- | `docs_list` / `docs_get` | Product docs (`coderunner`, `coderunner-runtime`, `shell`, `design-standard`, `css-and-styling`, `databases`, `vault`, `feature-flags`, `authentication` are embedded; `access` and `m2m` are live pages, with an `offset` parameter to read past the 12,000-character page limit) |
84
+ | `quota_get` / `billing_summary` | Plan quota and billing for the workspace's **account** (account id is not the workspace id; billing needs organization Owner, Admin or Billing admin) |
85
+ | `docs_list` / `docs_get` | Product docs (`coderunner`, `coderunner-runtime`, `shell`, `design-standard`, `css-and-styling`, `databases`, `vault`, `feature-flags`, `authentication`, `service-access` are embedded; `access` and `m2m` are live pages, with an `offset` parameter to read past the 12,000-character page limit) |
86
86
  | `suggest_cli` | Command to run: login, deploy, create_app, init_app, publish_app, m2m_token, … |
87
87
 
88
88
  Never create `app_kind=coderunner`. Deploy with `gf deploy`.
package/dist/index.js CHANGED
@@ -516,9 +516,20 @@ the provider.
516
516
 
517
517
  ## Users and roles (Groundfloor mode)
518
518
 
519
- - **Invite user**: they set their own password from an emailed invitation.
520
- - **Add user**: you set a password now; they get an email with their sign-in
521
- address and role (never the password).
519
+ - **Invite user**: they set their own password from an emailed invitation
520
+ (the link works for 72 hours).
521
+ - **Add user**: they can sign in straight away. Prefer **Generate a temporary
522
+ password** (API: \`POST /v1/workspaces/{id}/users/add\` with
523
+ \`generate_password: true\`): it is returned once in \`temporary_password\`
524
+ with \`sign_in_url\`, never stored or emailed, and must be changed at first
525
+ sign-in. They also get an email with their sign-in address and role (never
526
+ the password).
527
+ - Invite and add take an optional \`app_id\` so the email's sign-in link opens
528
+ that app.
529
+ - Inviting or adding someone who can already sign in (a password, another
530
+ credential or single sign-on) returns 409 and sends nothing. Use
531
+ \`POST \u2026/users/{user_id}/reset-password\` for them instead.
532
+ - App users never appear on the portal **Team** page.
522
533
  - Each user has a role: built-in \`viewer\`, \`user\`, \`admin\`, or a custom role
523
534
  created on the **Roles** tab. Roles here are unrelated to Portal team roles.
524
535
  - The role decides what the user reaches once granted on **Site access**
@@ -549,6 +560,47 @@ the provider.
549
560
  stored custom server settings and password. Only send \`mail\` when the user
550
561
  means to change email settings.
551
562
 
563
+ ## When an invite email isn't sent
564
+
565
+ Never report an invite as delivered without checking. Invite, add, resend and
566
+ reset responses, and every user in \`GET /v1/workspaces/{id}/users\`, carry:
567
+
568
+ - \`email_sent\`: \`true\` sent, \`false\` not sent, \`null\` unknown.
569
+ - \`email_skipped_reason\`: \`smtp_not_configured\` (no email server set up for
570
+ the workspace's sign-in email), \`send_failed\` (the server refused or
571
+ failed), \`delivery_unconfirmed\` (timed out; it may still arrive),
572
+ \`not_requested\` (sent with \`send_email: false\`), or \`null\`.
573
+ \`keycloak_smtp_missing\` is retired and no longer returned.
574
+ - \`email_attempted_at\` (\`null\` = invited before delivery was recorded),
575
+ \`invite_pending\`, \`password_change_required\`,
576
+ \`temporary_password_set_at\`, \`invite_link_expires_at\`.
577
+
578
+ If \`email_sent\` is not \`true\` for a pending user, tell the user plainly
579
+ and offer the fallbacks. In the Portal they are on the user's row under
580
+ **App authentication \u2192 Users**; all need workspace \`administer\` (M2M and
581
+ app-user tokens get 403):
582
+
583
+ 1. **Resend** (\`POST \u2026/users/{user_id}/resend-invite\`, pending users only):
584
+ at most once per 60 s and 5 times per 24 h; re-inviting a pending user
585
+ counts against the same limit (429 with \`Retry-After\`). If the reason is
586
+ \`send_failed\` or \`smtp_not_configured\`, fix email first (Branding \u2192
587
+ Email sending, Send test email) or resending will fail the same way.
588
+ 2. **Temp password** (\`POST \u2026/users/{user_id}/temporary-password\`, pending
589
+ or still on an unused temporary password): returns \`temporary_password\`
590
+ and \`sign_in_url\` once; no email is sent; same user id and roles; must be
591
+ changed at first sign-in. 504 = it may or may not be set, retry.
592
+ 3. **Copy link** (\`POST \u2026/users/{user_id}/invite-link\`, pending and enabled
593
+ users only): returns a one-time \`url\` (the portal's \`/join\` page) and
594
+ \`expires_at\` (24 h), once. The user opens it and chooses their own
595
+ password. Each call cancels the previous link; \`DELETE\` cancels it;
596
+ at most 10 per user per hour. Anyone holding the link can set the
597
+ password.
598
+
599
+ Temporary passwords and invite links are credentials. Show them only to the
600
+ person who asked for them, tell them to share privately, and never write them
601
+ to files, commits, logs or chat with anyone else. Once a user has set their
602
+ own password, none of these apply (409): use reset-password.
603
+
552
604
  ## Multi-tenant (customer companies inside one workspace)
553
605
 
554
606
  Turn on **Multi-tenant** in Settings to get a Tenants tab: one tenant per
@@ -563,11 +615,24 @@ Switching to None or External keeps users, roles and settings (switch back and
563
615
  they return). To remove them, turn sign-in off first, then **Delete Groundfloor
564
616
  login**. Access grants and M2M tokens are kept.
565
617
 
618
+ ## What app users' tokens can call
619
+
620
+ App users' tokens work only on: app list and by-slug, Data Vault collection
621
+ list/schema and record query/count/create/update/delete (per collection
622
+ grants), Files list/upload/download/delete (per Files grants) and
623
+ \`GET \u2026/flags/runtime\`. Everything else returns 403 "This API is not
624
+ available to app end users", whatever their role, \`admin\` included. A user
625
+ with no role at all gets 403 too.
626
+
566
627
  ## In a Shell app
567
628
 
568
629
  The signed-in user's JWT comes from \`window.__GF_GET_ACCESS_TOKEN__()\`. See
569
- \`docs_get\` id \`shell\`. Unattended workloads use M2M instead (\`docs_get\` id
570
- \`m2m\`), not an end-user login.
630
+ \`docs_get\` id \`shell\`. Roles are in the token's \`realm_access.roles\`
631
+ (Groundfloor mode). A Shell app's backend Service is public and must verify
632
+ that token itself (issuer and client ID from \`/v1/public/workspaces/by-host\`,
633
+ JWKS, RS256): \`docs_get\` id \`service-access\`.
634
+ Unattended workloads use M2M instead (\`docs_get\` id \`m2m\`), not an
635
+ end-user login.
571
636
  `.trim();
572
637
 
573
638
  // src/coderunner-runtime.ts
@@ -807,6 +872,11 @@ CMD ["npm", "start"]
807
872
 
808
873
  - Services follow the \`npm start\` / \`PORT\` contract: Node needs \`scripts.start\` if CMD is \`npm start\`.
809
874
  - Prefer curling the Deployment URL (\`/health\`) over \`\u2026/run\`.
875
+ - **The Deployment URL (and any App domain on it) is public.** Groundfloor
876
+ checks no identity. A Service that is a Shell app's backend must verify the
877
+ user's token itself, and allow the Shell origin for CORS. Off-the-shelf
878
+ images (port 80 defaults, MySQL, local uploads, sessions): \`docs_get\` id
879
+ \`service-access\`.
810
880
 
811
881
  ## Latest deploy rules (all types)
812
882
 
@@ -817,6 +887,11 @@ CMD ["npm", "start"]
817
887
  \`externalEndpoint\` and status 4 means the container never became ready.
818
888
  \`gf deploy\` prints the last Process Log lines, ids, and a Portal link on
819
889
  failure. Fetch more with \`gf coderunner logs -c <id|slug>\`.
890
+ - \`coderunner_get_status\` (\`GET \u2026/coderunners/{id}/status\`) returns
891
+ \`deployment_status\` (same 1\u20137 codes) and \`deployment_status_message\` for
892
+ the current deployment. While it is 4 (Failed), an App address or custom
893
+ domain routed to it answers 503, and the Portal app page shows "Deployment
894
+ failed".
820
895
  - Strip \`.env*\`, \`.git\`, \`node_modules\` from ZIPs. Put secrets in
821
896
  \`gf secrets\` / deploy \`-e\`, never in the archive.
822
897
  - Zip **contents** at root, not a wrapping folder.
@@ -899,10 +974,14 @@ An M2M client can also read secret **values** directly at runtime instead
899
974
  of (or alongside) \`--secret\` deploy-time injection: grant it the
900
975
  **\`secrets:read\`** scope (same Portal **Administer \u2192 M2M tokens** screen,
901
976
  per-client toggle), then call
902
- \`GET {CONTROLPLANE_URL}/v1/workspaces/{workspace_id}/secrets/{key}\` with
903
- \`Authorization: Bearer\` from \`gf m2m token\` locally, or the deployed
977
+ \`GET {CONTROLPLANE_URL}/v1/workspaces/{workspace_id}/secrets/{key}?environment_id=$GROUNDFLOOR_ENVIRONMENT_ID\`
978
+ with \`Authorization: Bearer\` from \`gf m2m token\` locally, or the deployed
904
979
  workload's injected \`GROUNDFLOOR_M2M_CLIENT_ID\`/\`GROUNDFLOOR_M2M_CLIENT_SECRET\`
905
- exchanged the same way. This path is:
980
+ exchanged the same way. **Always pass \`environment_id\`**
981
+ (\`GROUNDFLOOR_ENVIRONMENT_ID\` is injected on deploy for a Coderunner bound to
982
+ an environment) to choose which environment's value is read: omitted or
983
+ empty, it resolves to the workspace's \`dev\` environment (or its only active
984
+ one), so a prod workload silently reads dev secrets. This path is:
906
985
 
907
986
  - **Read-only** \u2014 \`secrets:read\` never grants \`upsert\`/\`delete\`, regardless
908
987
  of the client's writer relation on Vault/Files.
@@ -911,7 +990,9 @@ exchanged the same way. This path is:
911
990
  - **Workspace-wide, not per-key or per-environment** \u2014 granting the scope
912
991
  exposes every secret in the workspace across all environments to that
913
992
  client's code, same class of trade as the existing Vault/Files writer
914
- grant. Not a fit for isolating one secret to one Coderunner.
993
+ grant. \`environment_id\` only selects which environment's value is
994
+ returned; it is not an access boundary. Not a fit for isolating one secret
995
+ (or one environment) to one Coderunner.
915
996
 
916
997
  Prefer it over \`--secret\` injection when you want live rotation and can
917
998
  do the fetch yourself; keep using \`--secret\` injection for SDKs that only
@@ -933,8 +1014,9 @@ documented limit.
933
1014
  Plan quota and usage are account-scoped, not workspace-scoped. The **account
934
1015
  id is not the workspace id**: read \`account_id\` from \`workspaces_list\`. If a
935
1016
  deploy is refused or you need to know what is in use, call \`quota_get\`.
936
- \`billing_summary\` shows spend, and only Owners and Billing admins can read it
937
- (others get 403).
1017
+ \`billing_summary\` shows spend, and only organization Owners, Admins and
1018
+ Billing admins can read it (others get 403). A Billing admin has no workspace
1019
+ access at all.
938
1020
 
939
1021
  ## Agent actions
940
1022
 
@@ -994,16 +1076,26 @@ deploy a dedicated PostgreSQL first.
994
1076
  ## Options
995
1077
 
996
1078
  - PostgreSQL (shared) \u2014 platform-managed, already there. Default for app data
997
- and Vault. No deploy step. This is NOT a workspace-owned resource: it does
998
- not appear on the customer Databases page, and there is nothing to scale,
999
- back up, or delete yourself \u2014 it's offered, not provisioned. Query via
1000
- Control Plane vault: POST /v1/workspaces/{id}/vault/collections/{name}/query
1079
+ and Vault. No deploy step. The portal Databases page lists it first as
1080
+ "Shared PostgreSQL" (Default, connected as \`postgresql\`), but it is NOT a
1081
+ workspace-owned resource: no credentials, and nothing to scale, back up, or
1082
+ delete yourself \u2014 it's offered, not provisioned. Query via Control Plane
1083
+ vault: POST /v1/workspaces/{id}/vault/collections/{name}/query
1001
1084
  - Dedicated PostgreSQL you deploy and own \u2014 portal Data \u2192 Databases, or
1002
1085
  POST /v1/workspaces/{id}/managed-databases with engine postgres. Heavier
1003
1086
  than the shared default: you own its scaling, backups, and lifecycle. Use it
1004
1087
  when you want a database isolated to this workspace.
1005
1088
  - A database they already run \u2014 through Dataplane if they wire it, or direct.
1006
1089
 
1090
+ A dedicated PostgreSQL is usable through Vault only once Dataplane has it:
1091
+ \`GET /v1/workspaces/{id}/managed-databases\` returns \`dataplane_status\`
1092
+ (\`connected\` | \`not_connected\` | null while not up) and
1093
+ \`dataplane_connection_name\` (pass it as Vault \`connection\`). If it is
1094
+ \`not_connected\`, retry with
1095
+ \`POST \u2026/managed-databases/{managed_database_id}/dataplane/register\` (write; the
1096
+ portal's Retry button), or tell the user to. New dedicated databases get a
1097
+ daily backup schedule (30-day retention) where the cell has backup storage.
1098
+
1007
1099
  PostgreSQL is the only engine offered today, shared or dedicated. Do not offer
1008
1100
  other engines (ArangoDB, MongoDB, Redis, ClickHouse, Qdrant, \u2026) as available,
1009
1101
  even if you see them mentioned elsewhere. Do not call Dataplane hosts from apps
@@ -1119,9 +1211,10 @@ with no role gets 403.
1119
1211
  ### Shell federated app (browser)
1120
1212
 
1121
1213
  - **Token**: the Shell end-user JWT, \`await window.__GF_GET_ACCESS_TOKEN__()\`.
1122
- - **Permission**: workspace \`read\`. Site users get it from a recognised site
1123
- role (member/admin); self-registered users with no role get 403 (same rule
1124
- as Vault). No per-flag Access grant exists or is needed.
1214
+ - **Permission**: any role from the workspace's App authentication (built-in
1215
+ or custom); a signed-in user with no role gets 403 (same rule as Vault).
1216
+ App users are not workspace members. No per-flag Access grant exists or is
1217
+ needed.
1125
1218
  - **Environment**: \`window.__GF_ENVIRONMENT_ID__\` \u2014 the environment the app is
1126
1219
  bound to, published by the Shell before your remote mounts. Unset in local
1127
1220
  Dev Shell \u2192 server default (dev); \`VITE_GF_ENVIRONMENT_ID\` pins one.
@@ -1180,6 +1273,113 @@ active environments \xB7 400 bad or reserved key \xB7 422 value/value_type misma
1180
1273
  423 workspace frozen (writes).
1181
1274
  `.trim();
1182
1275
 
1276
+ // src/service-access-guide.ts
1277
+ var SERVICE_ACCESS_GUIDE = `
1278
+ # Who can reach a Service (and checking Shell tokens in a backend)
1279
+
1280
+ ## The rule
1281
+
1282
+ Every Coderunner Service is public. Its Deployment URL, and an App's domain
1283
+ routed to it, accept requests from anyone. Groundfloor routes the request but
1284
+ signs nobody in and checks no token. The Service decides who gets in.
1285
+
1286
+ | Service is\u2026 | Example | Do |
1287
+ |---|---|---|
1288
+ | Open to everyone | website, docs, public API, webhook receiver | Nothing (default) |
1289
+ | An app with its own logins | WordPress, Ghost, Metabase | Nothing from Groundfloor; its own login applies. Admin pages (\`/wp-admin\`) are public too |
1290
+ | Backend for a Shell app | API the Shell app calls with \`__GF_GET_ACCESS_TOKEN__()\` | Verify the user's token on **every** request |
1291
+
1292
+ Never tell a user that Shell sign-in, App authentication or Site access
1293
+ protects a Service. Anyone can call the Service directly.
1294
+
1295
+ ## Verifying a Shell user's token in a backend
1296
+
1297
+ 1. **Issuer and client ID**: \`GET {CONTROLPLANE_URL}/v1/public/workspaces/by-host?host=<shell-site-host>\`
1298
+ (unauthenticated) \u2192 \`{ workspace_id, mode, oidc: { issuer, client_id } | null, site_host }\`.
1299
+ Check \`workspace_id\` is the user's workspace. \`oidc\` is null in mode
1300
+ \`none\`. Groundfloor mode: per-workspace issuer, \`client_id\` \`shell\`.
1301
+ External mode: the provider's issuer and the client ID the customer
1302
+ entered. Look it up **once at setup**, never at runtime from the request's
1303
+ \`Host\` header (caller-controlled). Do not hard-code the issuer host (it
1304
+ differs per cell); copy the issuer **exactly, including any trailing \`/\`**
1305
+ (do not strip it before comparing \`iss\`) into non-secret env such as
1306
+ \`AUTH_ISSUER\` / \`AUTH_CLIENT_ID\` (Dockerfile \`ENV\`, since deploy-time
1307
+ env is not kept between deploys).
1308
+ 2. **Checks** (401 if any fails): RS256 signature against
1309
+ \`{issuer}/protocol/openid-connect/certs\` (External: the provider's
1310
+ \`jwks_uri\`), key chosen by the token's \`kid\` (the JWKS has several
1311
+ keys); \`iss\` equals the issuer exactly; \`exp\` present and in the future;
1312
+ the token was issued to the client ID:
1313
+ - Groundfloor mode: \`azp\` == client ID. Do not require an app-specific
1314
+ \`aud\` (none exists yet; tokens often carry \`aud: "account"\`, so with
1315
+ PyJWT set \`verify_aud: False\` and check the client yourself).
1316
+ - External mode: the client ID must appear in \`azp\` (or the provider's
1317
+ equivalent, e.g. \`cid\`, \`appid\`) **or** in \`aud\`. Never accept a
1318
+ token naming it in neither. If the provider allows open sign-up, "any
1319
+ signed-in user" means anyone: require a role.
1320
+ 403 when the token is valid but the role (or tenant) is wrong.
1321
+ 3. **Claims**: roles in \`realm_access.roles\` (built-in \`viewer\`/\`user\`/
1322
+ \`admin\` plus custom roles; ignore \`offline_access\`, \`uma_authorization\`,
1323
+ \`default-roles-*\`). Treat a missing or malformed \`realm_access\`/\`roles\`
1324
+ (not an object / not a list) as no roles \u2192 403, never a 500, and match
1325
+ whole role strings only. External mode: roles come from the customer's
1326
+ provider, in its own claim. For the user ID claim, follow the docs page
1327
+ /docs/apps/shell/sign-in#know-who-the-user-is.
1328
+ **Multi-tenant**: roles apply across every company (an \`admin\` in one
1329
+ company is admin everywhere), so filter every read and write by the
1330
+ \`organization\` claim too. It can hold more than one company: check the
1331
+ record's company is in it. Its exact form is unconfirmed; accept a list of
1332
+ names or an object keyed by name, anything else = no companies.
1333
+ 4. **Libraries**: Python PyJWT \`PyJWKClient(JWKS_URL, timeout=5)\` (it
1334
+ refetches the JWKS on every unknown \`kid\`, so keep the timeout short) +
1335
+ \`jwt.decode(token, key.key, algorithms=["RS256"], issuer=ISSUER,
1336
+ options={"require": ["exp", "iss"], "verify_aud": False})\`, then
1337
+ \`azp == CLIENT_ID\` (External: or \`cid\`/\`appid\`/\`aud\`), as a FastAPI
1338
+ dependency plus a \`require_role()\` dependency for 403. Node: \`jose\`
1339
+ \`createRemoteJWKSet\` + \`jwtVerify(token, JWKS, { issuer, algorithms:
1340
+ ["RS256"], requiredClaims: ["exp"] })\`, then the same client check; fail at
1341
+ startup if \`AUTH_ISSUER\` or \`AUTH_CLIENT_ID\` is unset (an undefined
1342
+ issuer disables jose's \`iss\` check). Full examples on the docs page.
1343
+ 5. **CORS**: the Shell site and the Service are different origins, so the
1344
+ browser preflights calls carrying \`Authorization\`. Allow the Shell site
1345
+ origin (and its custom domain), methods, and the \`Authorization\` and
1346
+ \`Content-Type\` headers (FastAPI \`CORSMiddleware\`; Express
1347
+ \`cors({ origin: ["https://<shell-site>"], allowedHeaders: ["Authorization", "Content-Type"] })\`,
1348
+ never bare \`cors()\`, which allows every origin), so preflight is answered
1349
+ before the token check. Never \`*\` with credentials; the bearer header
1350
+ needs no credentials mode.
1351
+
1352
+ ## Every app on the Shell site can use the user's token
1353
+
1354
+ Every app on a Shell site, including apps the customer didn't write, can get
1355
+ the signed-in user's token and call the customer's backends as that user, and
1356
+ a token from one app is accepted by every other app's backend in the
1357
+ workspace. Tell users to install only apps they trust and to check roles for
1358
+ anything sensitive. Per-app tokens, sign-in settings injected into the
1359
+ Service, and a verify helper are planned (no date). Do not promise per-app
1360
+ isolation.
1361
+
1362
+ ## Running off-the-shelf images as a Service
1363
+
1364
+ - Listen on 8080 (or \`PORT\`) on 0.0.0.0; many images default to 80, so
1365
+ reconfigure in the Dockerfile.
1366
+ - Managed databases are PostgreSQL only; MySQL/MariaDB/MongoDB apps
1367
+ (WordPress) need an external database.
1368
+ - **Finish the installer immediately**: apps with a first-visit installer
1369
+ (WordPress) can be claimed by anyone who opens the URL until it's completed.
1370
+ Tell the user to complete it as soon as the first deploy finishes.
1371
+ - Read secrets in an entrypoint script: pass the M2M credentials to curl on
1372
+ stdin (not argv), fail closed if the value is empty, \`unset\` the M2M
1373
+ credentials and token before \`exec\`ing the app.
1374
+ - Local disk is lost on restart, redeploy and scale-to-zero, and is per
1375
+ instance. Use the app's S3/object-storage plugin against external storage;
1376
+ Groundfloor Files is an API for code the customer writes, not an S3 endpoint.
1377
+ - More than one instance breaks in-memory/on-disk sessions; keep one instance
1378
+ or store sessions in the database. Set min instances \u2265 1 so the first visit
1379
+ doesn't wait.
1380
+ - Only the app's own login applies; Groundfloor sign-in can't front it today.
1381
+ `.trim();
1382
+
1183
1383
  // src/shell-guide.ts
1184
1384
  var SHELL_APP_GUIDE = `
1185
1385
  # Shell federated apps
@@ -1227,6 +1427,12 @@ outside Shell) so the breadcrumb does not show the raw id. Give that route
1227
1427
  routes (omit it or use \`false\` otherwise \u2014 never \`null\`). \`visibility:
1228
1428
  "public"\` makes a route reachable without login.
1229
1429
 
1430
+ Backends: secrets and server-side work go in a Coderunner Service the app
1431
+ calls with \`Authorization: Bearer \${await window.__GF_GET_ACCESS_TOKEN__()}\`.
1432
+ Shell sign-in does **not** protect that Service: its URL is public, so it must
1433
+ verify the token on every request and allow the Shell origin for CORS
1434
+ (\`docs_get\` id \`service-access\`).
1435
+
1230
1436
  ## suggest_cli
1231
1437
 
1232
1438
  - create_app \u2192 \`gf apps create --name "<App Name>" --slug <portal-slug> --kind shell_federated\` (register the App first; publish fails with App not found otherwise)
@@ -1425,7 +1631,7 @@ var DOC_CATALOG = [
1425
1631
  path: "/docs/deploy",
1426
1632
  summary: "Workload types (function, job, schedule, service/Deployment), runtime contract (handler.js for function/job/schedule; npm start, PORT 8080 for service), Dockerfile for services, Coderunner identities for Vault, App authentication optional, local live data, gf deploy.",
1427
1633
  embedded: CODERUNNER_GUIDE,
1428
- lastUpdated: "2026-10-08"
1634
+ lastUpdated: "2026-10-14"
1429
1635
  },
1430
1636
  {
1431
1637
  id: "coderunner-runtime",
@@ -1435,13 +1641,21 @@ var DOC_CATALOG = [
1435
1641
  embedded: CODERUNNER_RUNTIME_CONTRACT,
1436
1642
  lastUpdated: "2026-10-08"
1437
1643
  },
1644
+ {
1645
+ id: "service-access",
1646
+ title: "Who can reach your Service",
1647
+ path: "/docs/deploy/securing-a-service",
1648
+ summary: "Every Service (Deployment URL or App domain) is public; Groundfloor signs nobody in and checks no token. Open sites and apps with their own logins need nothing (finish installers like WordPress immediately); a Shell app's backend must verify the user's JWT on every request: issuer and client_id copied once from GET /v1/public/workspaces/by-host (never from the request Host header), RS256 via the issuer's JWKS by kid, exact iss, exp, azp == client_id in Groundfloor mode (External: azp/cid/appid or aud), no app aud yet; roles in realm_access.roles (malformed = no roles = 403); with Multi-tenant also filter by organization. CORS for the Shell origin. Every app on the Shell site can use the user's token against any of the workspace's backends until per-app tokens ship. Off-the-shelf images: port 8080, PostgreSQL only, ephemeral disk, sessions, min instances.",
1649
+ embedded: SERVICE_ACCESS_GUIDE,
1650
+ lastUpdated: "2026-10-14"
1651
+ },
1438
1652
  {
1439
1653
  id: "authentication",
1440
1654
  title: "Workspace Authentication",
1441
1655
  path: "/docs/access/app-authentication",
1442
- summary: "App end users sign in only to the app via the workspace App Authentication realm; no Groundfloor console account or workspace membership. Modes none / Groundfloor / external, users and roles, SSO, multi-tenant, sign-in email (Groundfloor email by default, or your own server). Optional; not required to create Apps or Coderunners. Separate from portal Team.",
1656
+ summary: "App end users sign in only to the app via the workspace App Authentication realm; no Groundfloor console account or workspace membership. Modes none / Groundfloor / external, users and roles, SSO, multi-tenant, sign-in email (Groundfloor email by default, or your own server). Invite delivery (email_sent, email_skipped_reason) and what to do when an invite isn't sent: resend, generated temporary password, one-time invite link; 409 for people who can already sign in. App users' tokens work only on the end-user routes. Optional; not required to create Apps or Coderunners. Separate from portal Team.",
1443
1657
  embedded: APP_AUTHENTICATION_GUIDE,
1444
- lastUpdated: "2026-10-13"
1658
+ lastUpdated: "2026-10-14"
1445
1659
  },
1446
1660
  {
1447
1661
  id: "access",
@@ -1467,7 +1681,7 @@ var DOC_CATALOG = [
1467
1681
  path: "/docs/apps/shell",
1468
1682
  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.",
1469
1683
  embedded: SHELL_APP_GUIDE,
1470
- lastUpdated: "2026-10-08"
1684
+ lastUpdated: "2026-10-14"
1471
1685
  },
1472
1686
  {
1473
1687
  id: "design-standard",
@@ -1489,9 +1703,9 @@ var DOC_CATALOG = [
1489
1703
  id: "databases",
1490
1704
  title: "Databases",
1491
1705
  path: "/docs/data/databases",
1492
- summary: "Dataplane is not a database. PostgreSQL (shared, platform-managed) is ready through Dataplane with no deploy step. A dedicated PostgreSQL you deploy and own is a separate, heavier path. PostgreSQL is the only engine offered.",
1706
+ summary: "Dataplane is not a database. PostgreSQL (shared, platform-managed) is ready through Dataplane with no deploy step. A dedicated PostgreSQL you deploy and own is a separate, heavier path; check its dataplane_status before using it in Vault. PostgreSQL is the only engine offered.",
1493
1707
  embedded: DATABASES_GUIDE,
1494
- lastUpdated: "2026-09-26"
1708
+ lastUpdated: "2026-10-14"
1495
1709
  },
1496
1710
  {
1497
1711
  id: "vault",
@@ -1507,7 +1721,7 @@ var DOC_CATALOG = [
1507
1721
  path: "/docs/configure/feature-flags",
1508
1722
  summary: "Built-in per-environment feature flags (toggles / config values) \u2014 use these, not a Vault collection. gf flags ls|set|compare|promote. Apps read GET \u2026/flags/runtime: Shell apps with the end-user JWT + window.__GF_ENVIRONMENT_ID__ (starter-kit useFlag), Coderunners with gf m2m token + GROUNDFLOOR_ENVIRONMENT_ID. Default environment is dev.",
1509
1723
  embedded: FLAGS_GUIDE,
1510
- lastUpdated: "2026-10-08"
1724
+ lastUpdated: "2026-10-14"
1511
1725
  },
1512
1726
  {
1513
1727
  id: "flags-api",
@@ -1515,7 +1729,7 @@ var DOC_CATALOG = [
1515
1729
  path: "/docs/api/flags",
1516
1730
  summary: "REST reference for flags: list/get/put/delete per environment_id (omitted \u2192 workspace default, dev), compare, promote, runtime read. Key format, value_type validation, auth per method (M2M read-only), error codes.",
1517
1731
  embedded: FLAGS_GUIDE,
1518
- lastUpdated: "2026-10-08"
1732
+ lastUpdated: "2026-10-14"
1519
1733
  },
1520
1734
  {
1521
1735
  id: "scheduled-jobs",
@@ -1523,7 +1737,7 @@ var DOC_CATALOG = [
1523
1737
  path: "/docs/deploy/schedules",
1524
1738
  summary: "workload_type=schedule plus cron. gf coderunner status shows type/schedule/last_job. Use Process Log, not a public URL.",
1525
1739
  embedded: CODERUNNER_GUIDE,
1526
- lastUpdated: "2026-10-08"
1740
+ lastUpdated: "2026-10-14"
1527
1741
  }
1528
1742
  ];
1529
1743
  function docUrl(entry) {
@@ -1818,7 +2032,7 @@ server.tool(
1818
2032
  default_environment_id: flags?.environment_id ?? null,
1819
2033
  default_environment_flags: flags ? flags.flags.map((f) => ({ key: f.key, value_type: f.value_type, value: f.value })) : null,
1820
2034
  manage: "gf flags ls|get|set|rm|compare|promote -e <env id or name>. Omitting -e uses the default environment (dev).",
1821
- shell_app: "Starter-kit src/lib/flags.ts: useFlag('key', fallback). Calls GET /v1/workspaces/{id}/flags/runtime?environment_id=window.__GF_ENVIRONMENT_ID__ with the Shell end-user JWT (needs a site role \u2192 workspace read).",
2035
+ shell_app: "Starter-kit src/lib/flags.ts: useFlag('key', fallback). Calls GET /v1/workspaces/{id}/flags/runtime?environment_id=window.__GF_ENVIRONMENT_ID__ with the Shell end-user JWT (needs any App authentication role; app users are not workspace members).",
1822
2036
  coderunner: "GET \u2026/flags/runtime?environment_id=$GROUNDFLOOR_ENVIRONMENT_ID with an M2M token (gf m2m token). M2M is read-only on flags; workload-identity tokens are rejected."
1823
2037
  },
1824
2038
  coderunner_howto: {
@@ -2229,7 +2443,7 @@ server.tool(
2229
2443
  );
2230
2444
  server.tool(
2231
2445
  "billing_summary",
2232
- "Compute-unit usage and billing estimate for the organization that owns the workspace (account-scoped, not workspace-scoped). Only Owners and Billing admins may read it; others get 403. The response notes whether it is an estimate.",
2446
+ "Compute-unit usage and billing estimate for the organization that owns the workspace (account-scoped, not workspace-scoped). Only organization Owners, Admins and Billing admins may read it; others get 403. The response notes whether it is an estimate.",
2233
2447
  {
2234
2448
  workspace_id: wsId,
2235
2449
  account_id: accountId,
@@ -2278,7 +2492,7 @@ server.tool(
2278
2492
  "How-to for Coderunner, Shell, design-standard, css-and-styling, databases, access, m2m, authentication (app end users). Pass id from docs_list. Prefer id coderunner or coderunner-runtime before deploying. For site grants use id access. For Coderunner Vault/Files use id m2m.",
2279
2493
  {
2280
2494
  id: z.string().min(1).describe(
2281
- "Catalog id, e.g. coderunner | coderunner-runtime | shell | design-standard | css-and-styling | databases | agents | developers | authentication | access | m2m"
2495
+ "Catalog id, e.g. coderunner | coderunner-runtime | service-access | shell | design-standard | css-and-styling | databases | agents | developers | authentication | access | m2m"
2282
2496
  ),
2283
2497
  offset: z.number().int().min(0).optional().describe(
2284
2498
  "Live (non-embedded) pages only: character offset to resume from. Pass nextOffset from the previous result when truncated is true."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groundfloorcloud/mcp",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Groundfloor MCP — inspect workspace state and docs; take actions with the gf CLI.",
5
5
  "type": "module",
6
6
  "bin": {