@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.
- package/README.md +2 -2
- package/dist/index.js +244 -30
- 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
|
-
|
|
521
|
-
|
|
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\`.
|
|
570
|
-
|
|
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}\`
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
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**:
|
|
1123
|
-
|
|
1124
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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."
|