@groundfloorcloud/mcp 0.2.3 → 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 +1 -1
- package/dist/index.js +144 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,7 +82,7 @@ Use the prefixed names: `GROUNDFLOOR_API_URL`, `GROUNDFLOOR_WORKSPACE_ID`, `GROU
|
|
|
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
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` are embedded; `access` and `m2m` are live pages, with an `offset` parameter to read past the 12,000-character page limit) |
|
|
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
|
@@ -627,8 +627,12 @@ with no role at all gets 403 too.
|
|
|
627
627
|
## In a Shell app
|
|
628
628
|
|
|
629
629
|
The signed-in user's JWT comes from \`window.__GF_GET_ACCESS_TOKEN__()\`. See
|
|
630
|
-
\`docs_get\` id \`shell\`.
|
|
631
|
-
|
|
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.
|
|
632
636
|
`.trim();
|
|
633
637
|
|
|
634
638
|
// src/coderunner-runtime.ts
|
|
@@ -868,6 +872,11 @@ CMD ["npm", "start"]
|
|
|
868
872
|
|
|
869
873
|
- Services follow the \`npm start\` / \`PORT\` contract: Node needs \`scripts.start\` if CMD is \`npm start\`.
|
|
870
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\`.
|
|
871
880
|
|
|
872
881
|
## Latest deploy rules (all types)
|
|
873
882
|
|
|
@@ -965,10 +974,14 @@ An M2M client can also read secret **values** directly at runtime instead
|
|
|
965
974
|
of (or alongside) \`--secret\` deploy-time injection: grant it the
|
|
966
975
|
**\`secrets:read\`** scope (same Portal **Administer \u2192 M2M tokens** screen,
|
|
967
976
|
per-client toggle), then call
|
|
968
|
-
\`GET {CONTROLPLANE_URL}/v1/workspaces/{workspace_id}/secrets/{key}\`
|
|
969
|
-
\`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
|
|
970
979
|
workload's injected \`GROUNDFLOOR_M2M_CLIENT_ID\`/\`GROUNDFLOOR_M2M_CLIENT_SECRET\`
|
|
971
|
-
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:
|
|
972
985
|
|
|
973
986
|
- **Read-only** \u2014 \`secrets:read\` never grants \`upsert\`/\`delete\`, regardless
|
|
974
987
|
of the client's writer relation on Vault/Files.
|
|
@@ -977,7 +990,9 @@ exchanged the same way. This path is:
|
|
|
977
990
|
- **Workspace-wide, not per-key or per-environment** \u2014 granting the scope
|
|
978
991
|
exposes every secret in the workspace across all environments to that
|
|
979
992
|
client's code, same class of trade as the existing Vault/Files writer
|
|
980
|
-
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.
|
|
981
996
|
|
|
982
997
|
Prefer it over \`--secret\` injection when you want live rotation and can
|
|
983
998
|
do the fetch yourself; keep using \`--secret\` injection for SDKs that only
|
|
@@ -1258,6 +1273,113 @@ active environments \xB7 400 bad or reserved key \xB7 422 value/value_type misma
|
|
|
1258
1273
|
423 workspace frozen (writes).
|
|
1259
1274
|
`.trim();
|
|
1260
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
|
+
|
|
1261
1383
|
// src/shell-guide.ts
|
|
1262
1384
|
var SHELL_APP_GUIDE = `
|
|
1263
1385
|
# Shell federated apps
|
|
@@ -1305,6 +1427,12 @@ outside Shell) so the breadcrumb does not show the raw id. Give that route
|
|
|
1305
1427
|
routes (omit it or use \`false\` otherwise \u2014 never \`null\`). \`visibility:
|
|
1306
1428
|
"public"\` makes a route reachable without login.
|
|
1307
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
|
+
|
|
1308
1436
|
## suggest_cli
|
|
1309
1437
|
|
|
1310
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)
|
|
@@ -1513,6 +1641,14 @@ var DOC_CATALOG = [
|
|
|
1513
1641
|
embedded: CODERUNNER_RUNTIME_CONTRACT,
|
|
1514
1642
|
lastUpdated: "2026-10-08"
|
|
1515
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
|
+
},
|
|
1516
1652
|
{
|
|
1517
1653
|
id: "authentication",
|
|
1518
1654
|
title: "Workspace Authentication",
|
|
@@ -1545,7 +1681,7 @@ var DOC_CATALOG = [
|
|
|
1545
1681
|
path: "/docs/apps/shell",
|
|
1546
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.",
|
|
1547
1683
|
embedded: SHELL_APP_GUIDE,
|
|
1548
|
-
lastUpdated: "2026-10-
|
|
1684
|
+
lastUpdated: "2026-10-14"
|
|
1549
1685
|
},
|
|
1550
1686
|
{
|
|
1551
1687
|
id: "design-standard",
|
|
@@ -2356,7 +2492,7 @@ server.tool(
|
|
|
2356
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.",
|
|
2357
2493
|
{
|
|
2358
2494
|
id: z.string().min(1).describe(
|
|
2359
|
-
"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"
|
|
2360
2496
|
),
|
|
2361
2497
|
offset: z.number().int().min(0).optional().describe(
|
|
2362
2498
|
"Live (non-embedded) pages only: character offset to resume from. Pass nextOffset from the previous result when truncated is true."
|