@groundfloorcloud/mcp 0.1.16 → 0.2.0
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 +22 -8
- package/dist/index.js +468 -93
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -63,6 +63,12 @@ which node
|
|
|
63
63
|
|
|
64
64
|
The `command` must be your Node (from `which node`), not `npx` and not Cursor’s helper Node. Do not put a JWT in mcp.json.
|
|
65
65
|
|
|
66
|
+
### Environment overrides
|
|
67
|
+
|
|
68
|
+
Use the prefixed names: `GROUNDFLOOR_API_URL`, `GROUNDFLOOR_WORKSPACE_ID`, `GROUNDFLOOR_TOKEN` (CI only; normally the `gf login` session is used).
|
|
69
|
+
|
|
70
|
+
**Deprecated:** the unprefixed `CP_API`, `WORKSPACE_ID` and `TOKEN` are still honoured as fallbacks, but they collide with unrelated variables in a shell. When one of them supplies a value the server writes a one-line notice to stderr (variable name only, never the value) and they will be removed in a later release.
|
|
71
|
+
|
|
66
72
|
## Tools
|
|
67
73
|
|
|
68
74
|
| Tool | Role |
|
|
@@ -71,30 +77,38 @@ The `command` must be your Node (from `which node`), not `npx` and not Cursor’
|
|
|
71
77
|
| `workspaces_list` | List; switch with `gf workspaces use` |
|
|
72
78
|
| `coderunner_list` / `_get_status` / `_get_logs` / `_process_log` | Inspect workloads (Process Log does not need a job_id) |
|
|
73
79
|
| `environments_list` | List workspace environments |
|
|
74
|
-
| `coderunner_cancel_deployment` |
|
|
80
|
+
| `coderunner_cancel_deployment` | **Destructive.** Deletes one deployment (`deployment_id` is required); if it is the live one, the service goes down. Always the selected workspace (no per-call `workspace_id`) |
|
|
75
81
|
| `apps_list` / `apps_get` | Inspect product Apps |
|
|
76
|
-
| `secrets_list` / `files_list` / `
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
82
|
+
| `secrets_list` / `files_list` / `dataplane_status` | Inspect (no secrets values) |
|
|
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) |
|
|
86
|
+
| `suggest_cli` | Command to run: login, deploy, create_app, init_app, publish_app, m2m_token, … |
|
|
79
87
|
|
|
80
88
|
Never create `app_kind=coderunner`. Deploy with `gf deploy`.
|
|
81
89
|
|
|
82
|
-
**Shell apps:** `docs_get` id `shell`. Always `gf apps
|
|
83
|
-
then `npm run release` and `gf apps publish --path release.zip`. Do not invent
|
|
90
|
+
**Shell apps:** `docs_get` id `shell`. Always `gf apps create --name … --slug <portal-slug> --kind shell_federated`,
|
|
91
|
+
then `gf apps init --slug <portal-slug>`, `npm install`, `npm run release` and `gf apps publish --path release.zip`. Do not invent
|
|
84
92
|
`App.tsx` / `vite.config`, zip `dist/`, or upload only `remoteEntry.js`.
|
|
85
93
|
Own CSS framework: `docs_get` id `css-and-styling` (`FederatedStyleRoot`).
|
|
86
94
|
|
|
95
|
+
**App end users:** `docs_get` id `authentication`. They sign in only to the app via the workspace App Authentication realm; they have no Groundfloor console account or workspace membership.
|
|
96
|
+
|
|
97
|
+
**Tool annotations:** every tool except `coderunner_cancel_deployment` is marked `readOnlyHint`; the cancel tool is marked `destructiveHint`.
|
|
98
|
+
|
|
87
99
|
**Databases:** `docs_get` id `databases`. Dataplane is not a database. Shared
|
|
88
100
|
PostgreSQL is ready through Dataplane; a dedicated PostgreSQL is what you can
|
|
89
101
|
deploy today. No other engines are offered.
|
|
90
102
|
|
|
91
|
-
**Coderunner how-to** is embedded: `docs_get` id `coderunner
|
|
92
|
-
`coderunner-overview`
|
|
103
|
+
**Coderunner how-to** is embedded: `docs_get` id `coderunner`. The former
|
|
104
|
+
`coderunner-overview` id is no longer listed by `docs_list`, but `docs_get`
|
|
105
|
+
still accepts it as an alias and returns the same page. That page covers:
|
|
93
106
|
|
|
94
107
|
- Types: `function`, `job`, `schedule`, `service` (**also called Deployment**)
|
|
95
108
|
- App authentication is optional (end-user login); not required to create a coderunner
|
|
96
109
|
- **Dockerfile is required** for `service` / Deployment
|
|
97
110
|
- Runtime contract: Node `scripts.start`, listen on `PORT`/8080, bind `0.0.0.0` (`docs_get` id `coderunner-runtime`)
|
|
111
|
+
- Handler signature `(context, payload)` and the HTTP return shape, plus verified limits
|
|
98
112
|
- Local development against live vault/files/secrets (`gf env`; as the workload use `gf m2m token`, not `gf token`)
|
|
99
113
|
|
|
100
114
|
`workspace_context` includes a `coderunner_howto` summary so agents see this
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
|
|
6
|
+
// src/server.ts
|
|
7
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
8
|
import { z } from "zod";
|
|
7
9
|
|
|
8
10
|
// ../cli/src/auth/oauth.ts
|
|
@@ -95,7 +97,7 @@ var EXPIRY_SKEW_MS = 3e4;
|
|
|
95
97
|
var NotLoggedInError = class extends Error {
|
|
96
98
|
constructor() {
|
|
97
99
|
super(
|
|
98
|
-
"Not logged in. Set GROUNDFLOOR_TOKEN, or run `gf login` first."
|
|
100
|
+
"Not logged in. Set GROUNDFLOOR_TOKEN to a Groundfloor access token (e.g. from `gf m2m token`), or run `gf login` first."
|
|
99
101
|
);
|
|
100
102
|
this.name = "NotLoggedInError";
|
|
101
103
|
}
|
|
@@ -129,11 +131,26 @@ function sessionFromTokens(tokens, ctx) {
|
|
|
129
131
|
function isFresh(session) {
|
|
130
132
|
return Date.now() < session.expiresAt - EXPIRY_SKEW_MS;
|
|
131
133
|
}
|
|
134
|
+
function isExpiredJwt(token) {
|
|
135
|
+
const exp = decodeJwt(token)?.exp;
|
|
136
|
+
return typeof exp === "number" && Date.now() >= exp * 1e3 - EXPIRY_SKEW_MS;
|
|
137
|
+
}
|
|
132
138
|
async function getValidAccessToken() {
|
|
133
139
|
if (accessTokenProvider) {
|
|
134
140
|
return accessTokenProvider();
|
|
135
141
|
}
|
|
142
|
+
const envToken = process.env.GROUNDFLOOR_TOKEN?.trim();
|
|
143
|
+
let envTokenExpired = false;
|
|
144
|
+
if (envToken) {
|
|
145
|
+
if (!isExpiredJwt(envToken)) return envToken;
|
|
146
|
+
envTokenExpired = true;
|
|
147
|
+
}
|
|
136
148
|
const session = await readAuth();
|
|
149
|
+
if (envTokenExpired && (!session || !session.accessToken)) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
"GROUNDFLOOR_TOKEN has expired and there is no saved `gf login` session. Mint a fresh token (e.g. `gf m2m token`) or run `gf login`."
|
|
152
|
+
);
|
|
153
|
+
}
|
|
137
154
|
if (!session || !session.accessToken) {
|
|
138
155
|
throw new NotLoggedInError();
|
|
139
156
|
}
|
|
@@ -305,17 +322,6 @@ function getProcessLogs(apiUrl, workspaceId, query = {}) {
|
|
|
305
322
|
`/v1/workspaces/${encodeURIComponent(workspaceId)}/logs${qs ? `?${qs}` : ""}`
|
|
306
323
|
);
|
|
307
324
|
}
|
|
308
|
-
function domainsBase(workspaceId, coderunnerId) {
|
|
309
|
-
return `/v1/workspaces/${encodeURIComponent(
|
|
310
|
-
workspaceId
|
|
311
|
-
)}/coderunners/${encodeURIComponent(coderunnerId)}/domains`;
|
|
312
|
-
}
|
|
313
|
-
function listDomains(apiUrl, workspaceId, coderunnerId) {
|
|
314
|
-
return cpGet(
|
|
315
|
-
apiUrl,
|
|
316
|
-
domainsBase(workspaceId, coderunnerId)
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
325
|
function wsPath(workspaceId, suffix) {
|
|
320
326
|
return `/v1/workspaces/${encodeURIComponent(workspaceId)}${suffix}`;
|
|
321
327
|
}
|
|
@@ -411,13 +417,32 @@ function trimSlash(url) {
|
|
|
411
417
|
}
|
|
412
418
|
var MISSING_TOKEN = "Not signed in. Run `gf login` (or `gf login --stage`) so MCP can use ~/.groundfloor. Do not paste JWTs into Cursor MCP config.";
|
|
413
419
|
var MISSING_WORKSPACE = "No workspace selected. Run `gf workspaces` then `gf workspaces use <uuid>`, or pass workspace_id on this tool.";
|
|
420
|
+
var reportedSources = /* @__PURE__ */ new Set();
|
|
421
|
+
function noteDeprecatedEnv(setting, used, preferred) {
|
|
422
|
+
const key = `${setting}:${used}`;
|
|
423
|
+
if (reportedSources.has(key)) return;
|
|
424
|
+
reportedSources.add(key);
|
|
425
|
+
console.error(
|
|
426
|
+
`groundfloor MCP: ${setting} taken from deprecated env var ${used}; set ${preferred} instead (unprefixed names will be removed).`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
function pick(setting, preferred, deprecated) {
|
|
430
|
+
const a = process.env[preferred];
|
|
431
|
+
if (a) return a;
|
|
432
|
+
const b = process.env[deprecated];
|
|
433
|
+
if (b) {
|
|
434
|
+
noteDeprecatedEnv(setting, deprecated, preferred);
|
|
435
|
+
return b;
|
|
436
|
+
}
|
|
437
|
+
return "";
|
|
438
|
+
}
|
|
414
439
|
async function resolveMcpConfig(overrides) {
|
|
415
440
|
const file = await resolveConfig();
|
|
416
441
|
const apiUrl = trimSlash(
|
|
417
|
-
|
|
442
|
+
pick("API URL", "GROUNDFLOOR_API_URL", "CP_API") || file.apiUrl || CELLS.production.apiUrl
|
|
418
443
|
);
|
|
419
|
-
const workspaceId = overrides?.workspaceId ||
|
|
420
|
-
const envToken = (
|
|
444
|
+
const workspaceId = overrides?.workspaceId || pick("workspace id", "GROUNDFLOOR_WORKSPACE_ID", "WORKSPACE_ID") || file.workspaceId || "";
|
|
445
|
+
const envToken = pick("access token", "GROUNDFLOOR_TOKEN", "TOKEN").trim();
|
|
421
446
|
if (envToken) {
|
|
422
447
|
setAccessTokenProvider(async () => envToken);
|
|
423
448
|
} else {
|
|
@@ -444,6 +469,85 @@ async function resolveMcpConfig(overrides) {
|
|
|
444
469
|
return { apiUrl, workspaceId, token };
|
|
445
470
|
}
|
|
446
471
|
|
|
472
|
+
// src/cp-get.ts
|
|
473
|
+
async function cpGetJson(apiUrl, token, path2) {
|
|
474
|
+
const res = await fetch(`${apiUrl}${path2}`, {
|
|
475
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "application/json" }
|
|
476
|
+
});
|
|
477
|
+
if (!res.ok) {
|
|
478
|
+
let detail = res.statusText;
|
|
479
|
+
try {
|
|
480
|
+
const body = await res.json();
|
|
481
|
+
if (body?.detail !== void 0) {
|
|
482
|
+
detail = formatCpErrorDetail(body.detail, res.statusText);
|
|
483
|
+
}
|
|
484
|
+
} catch {
|
|
485
|
+
}
|
|
486
|
+
throw new CpError(res.status, `${res.status} ${detail}`);
|
|
487
|
+
}
|
|
488
|
+
return await res.json();
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/app-authentication-guide.ts
|
|
492
|
+
var APP_AUTHENTICATION_GUIDE = `
|
|
493
|
+
# App authentication (end users of a customer's app)
|
|
494
|
+
|
|
495
|
+
## The rule
|
|
496
|
+
|
|
497
|
+
People who use an app built on Groundfloor sign in **only to that app**,
|
|
498
|
+
through the workspace's **App authentication** realm. They do **not** get a
|
|
499
|
+
Groundfloor console (Client Portal) account and are **not** workspace members.
|
|
500
|
+
Portal team roles (Owner, Admin, Writer) are a separate system and never apply
|
|
501
|
+
to app end users. Do not build, suggest or provision Portal logins for them.
|
|
502
|
+
|
|
503
|
+
App authentication is **optional**. It is not needed to create Apps or deploy
|
|
504
|
+
Coderunners, and it never changes how the team signs in to the Portal.
|
|
505
|
+
|
|
506
|
+
## Sign-in modes (Portal: Administer \u2192 App authentication, Admin or Owner)
|
|
507
|
+
|
|
508
|
+
| Mode | End users sign in with | Use when |
|
|
509
|
+
|---|---|---|
|
|
510
|
+
| None | Nothing | Apps are public or handle access themselves |
|
|
511
|
+
| Groundfloor | A hosted sign-in page; users, roles, SSO and branding managed in the Portal | You want sign-in without running an identity provider |
|
|
512
|
+
| External | Your own OpenID Connect provider (Okta, Entra ID, Auth0) | Users already have accounts there |
|
|
513
|
+
|
|
514
|
+
For External, register \`https://<your-shell-site>/*\` as a redirect address with
|
|
515
|
+
the provider.
|
|
516
|
+
|
|
517
|
+
## Users and roles (Groundfloor mode)
|
|
518
|
+
|
|
519
|
+
- **Invite user**: they set their own password (email needs Branding set up).
|
|
520
|
+
- **Add user**: you set a password now; no email is sent.
|
|
521
|
+
- Each user has a role: built-in \`viewer\`, \`user\`, \`admin\`, or a custom role
|
|
522
|
+
created on the **Roles** tab. Roles here are unrelated to Portal team roles.
|
|
523
|
+
- The role decides what the user reaches once granted on **Site access**
|
|
524
|
+
(Data Vault collections, workspace-wide Files, App launch). The built-in
|
|
525
|
+
\`admin\` role reaches everything. A signed-in user with no recognised role
|
|
526
|
+
gets 403 on role-gated endpoints (Vault, feature flags).
|
|
527
|
+
- SSO (OIDC or SAML) can be connected per workspace; role names in the
|
|
528
|
+
provider's tokens must match the roles granted on Site access.
|
|
529
|
+
|
|
530
|
+
## Multi-tenant (customer companies inside one workspace)
|
|
531
|
+
|
|
532
|
+
Turn on **Multi-tenant** in Settings to get a Tenants tab: one tenant per
|
|
533
|
+
customer company, invite-only onboarding, per-tenant SSO. The user's token
|
|
534
|
+
carries the tenant in the \`organization\` claim; the app must keep each tenant's
|
|
535
|
+
data apart using that claim. Site access, data, files and M2M tokens stay shared
|
|
536
|
+
across the workspace.
|
|
537
|
+
|
|
538
|
+
## Turning it off
|
|
539
|
+
|
|
540
|
+
Switching to None or External keeps users, roles and settings (switch back and
|
|
541
|
+
they return). To remove them, turn sign-in off first, then **Delete Groundfloor
|
|
542
|
+
login**. Access grants and M2M tokens are kept.
|
|
543
|
+
|
|
544
|
+
## In a Shell app
|
|
545
|
+
|
|
546
|
+
The signed-in user's JWT comes from \`window.__GF_GET_ACCESS_TOKEN__()\`. See
|
|
547
|
+
\`docs_get\` id \`shell\`. Unattended workloads use M2M instead (\`docs_get\` id
|
|
548
|
+
\`m2m\`), not an end-user login.
|
|
549
|
+
`.trim();
|
|
550
|
+
|
|
447
551
|
// src/coderunner-runtime.ts
|
|
448
552
|
var CODERUNNER_RUNTIME_BODY = `
|
|
449
553
|
The contract depends on the workload type.
|
|
@@ -469,9 +573,58 @@ The contract depends on the workload type.
|
|
|
469
573
|
(\`Cannot find module './handler'\`, \`MODULE_NOT_FOUND\`) and the deployment
|
|
470
574
|
ends **Failed (status 4)**. Check \`gf coderunner logs -c <id>\`.
|
|
471
575
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
576
|
+
### The handler signature
|
|
577
|
+
|
|
578
|
+
Arguments are **\`(context, payload)\`** in that order. Export a function named
|
|
579
|
+
\`handler\` (CommonJS \`module.exports = { handler }\`; an ES module
|
|
580
|
+
\`export async function handler(context, payload)\` also works):
|
|
581
|
+
|
|
582
|
+
\`\`\`js
|
|
583
|
+
const handler = async (context, payload) => {
|
|
584
|
+
// your code
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
module.exports = { handler };
|
|
588
|
+
\`\`\`
|
|
589
|
+
|
|
590
|
+
- \`payload\` is the JSON input: the request body for a function, the run's input
|
|
591
|
+
for a job or schedule. Guard for a missing body (\`payload?.name\`).
|
|
592
|
+
- \`context\` is reserved for runtime information. Do not rely on its contents.
|
|
593
|
+
|
|
594
|
+
### Function return shape (HTTP)
|
|
595
|
+
|
|
596
|
+
Whatever the handler returns becomes the response; you may return anything.
|
|
597
|
+
The documented convention, used by every example, is:
|
|
598
|
+
|
|
599
|
+
\`\`\`js
|
|
600
|
+
const handler = async (context, payload) => {
|
|
601
|
+
if (!payload?.name) {
|
|
602
|
+
return {
|
|
603
|
+
statusCode: 400,
|
|
604
|
+
body: { error: "name is required" },
|
|
605
|
+
headers: { "content-type": "application/json" },
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
return {
|
|
609
|
+
statusCode: 200,
|
|
610
|
+
body: { greeting: \`Hello, \${payload.name}\` },
|
|
611
|
+
headers: { "content-type": "application/json" },
|
|
612
|
+
};
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
module.exports = { handler };
|
|
616
|
+
\`\`\`
|
|
617
|
+
|
|
618
|
+
Use \`200\`/\`201\` for success, \`400\` for bad input, \`401\`/\`403\` for auth,
|
|
619
|
+
\`404\` for not found, \`500\` for unexpected errors (no internals in the body).
|
|
620
|
+
Callers POST JSON to the Deployment URL with a bearer token, or use
|
|
621
|
+
\`gf coderunner run -c <slug> --payload '<json>'\`.
|
|
622
|
+
|
|
623
|
+
### Jobs and schedules: return value and failure
|
|
624
|
+
|
|
625
|
+
A job or schedule needs no return value. If an error **escapes** the handler the
|
|
626
|
+
run is marked **Failed**; if you catch it, the run succeeds and handling it is
|
|
627
|
+
up to you.
|
|
475
628
|
|
|
476
629
|
## Node: service (or any Dockerfile that runs \`npm start\`)
|
|
477
630
|
|
|
@@ -480,7 +633,7 @@ A \`service\` **requires** a root Dockerfile; you own \`CMD\`. If it is
|
|
|
480
633
|
and the process must follow the HTTP contract:
|
|
481
634
|
|
|
482
635
|
\`\`\`dockerfile
|
|
483
|
-
FROM node:
|
|
636
|
+
FROM node:22-alpine
|
|
484
637
|
WORKDIR /app
|
|
485
638
|
COPY . .
|
|
486
639
|
EXPOSE 8080
|
|
@@ -505,8 +658,14 @@ Offer GET \`/\` or \`/health\` for the probe. A Dockerfile whose \`CMD\` is not
|
|
|
505
658
|
|
|
506
659
|
## Python
|
|
507
660
|
|
|
508
|
-
|
|
509
|
-
\`
|
|
661
|
+
Function, job and schedule: \`handler.py\` at the ZIP root (with
|
|
662
|
+
\`requirements.txt\`) defining \`def handler(context, payload):\`, same argument
|
|
663
|
+
order, same return convention (\`statusCode\`, \`body\`,
|
|
664
|
+
\`headers\`; an exception escaping the handler fails a job run). \`print\` goes
|
|
665
|
+
to the Process Log.
|
|
666
|
+
|
|
667
|
+
A Dockerfile-based service binds \`0.0.0.0\` and \`PORT\` (default 8080), for
|
|
668
|
+
example \`uvicorn main:app --host 0.0.0.0 --port \${PORT:-8080}\`.
|
|
510
669
|
|
|
511
670
|
## Jobs and schedules
|
|
512
671
|
|
|
@@ -580,8 +739,9 @@ Pick **one** type per coderunner. Changing type later means a new coderunner.
|
|
|
580
739
|
\`/app/main.js\` that does \`require('./handler')\`; \`npm start\` is **not** run and
|
|
581
740
|
\`scripts.start\` is not required. Missing \`handler.js\` \u2192 crash loop
|
|
582
741
|
(\`Cannot find module './handler'\`), deployment **Failed (status 4)**.
|
|
583
|
-
\`gf deploy\` fails fast on this. Handler arguments
|
|
584
|
-
|
|
742
|
+
\`gf deploy\` fails fast on this. Handler arguments are \`(context, payload)\`; the
|
|
743
|
+
signature and the HTTP return shape (\`{ statusCode, body, headers }\`) are in the
|
|
744
|
+
Runtime contract section above.
|
|
585
745
|
- Node with your own Dockerfile (\`CMD ["npm","start"]\`) instead: listen on
|
|
586
746
|
\`process.env.PORT || 8080\`, bind **\`0.0.0.0\`** (health GET \`/\` or \`/health\`), and
|
|
587
747
|
define \`scripts.start\`.
|
|
@@ -613,7 +773,7 @@ Pick **one** type per coderunner. Changing type later means a new coderunner.
|
|
|
613
773
|
- Node example:
|
|
614
774
|
|
|
615
775
|
\`\`\`dockerfile
|
|
616
|
-
FROM node:
|
|
776
|
+
FROM node:22-alpine
|
|
617
777
|
WORKDIR /app
|
|
618
778
|
COPY package*.json ./
|
|
619
779
|
RUN npm ci --omit=dev
|
|
@@ -736,9 +896,28 @@ do the fetch yourself; keep using \`--secret\` injection for SDKs that only
|
|
|
736
896
|
read a literal \`process.env\` value. Either way, cache the revealed value
|
|
737
897
|
in your process rather than calling \`reveal\` on every request.
|
|
738
898
|
|
|
899
|
+
## Limits an agent will hit
|
|
900
|
+
|
|
901
|
+
Verified against the control plane source; anything not listed here is not a
|
|
902
|
+
documented limit.
|
|
903
|
+
|
|
904
|
+
- **M2M token endpoint**: 20 requests per minute (per client and per source).
|
|
905
|
+
Cache the token until near expiry; do not mint per request.
|
|
906
|
+
- **Vault collection list/query**: \`limit\` 1-500 (default 50).
|
|
907
|
+
- **Feature flag string values**: at most 10,000 characters.
|
|
908
|
+
- **\`docs_get\` live pages**: truncated at 12,000 characters; use \`offset\` to
|
|
909
|
+
read the rest.
|
|
910
|
+
|
|
911
|
+
Plan quota and usage are account-scoped, not workspace-scoped. The **account
|
|
912
|
+
id is not the workspace id**: read \`account_id\` from \`workspaces_list\`. If a
|
|
913
|
+
deploy is refused or you need to know what is in use, call \`quota_get\`.
|
|
914
|
+
\`billing_summary\` shows spend, and only Owners and Billing admins can read it
|
|
915
|
+
(others get 403).
|
|
916
|
+
|
|
739
917
|
## Agent actions
|
|
740
918
|
|
|
741
|
-
|
|
919
|
+
|
|
920
|
+
Inspect: \`workspace_context\`, \`quota_get\`, \`docs_get\` id \`coderunner\` or \`coderunner-runtime\`, \`suggest_cli\`.
|
|
742
921
|
Act: \`gf deploy\`, \`gf coderunner run|status|logs\`, \`gf m2m token\`, \`gf identities ls|mint\`, \`gf api\`. MCP does not deploy.
|
|
743
922
|
`.trim();
|
|
744
923
|
|
|
@@ -911,7 +1090,9 @@ One endpoint for every runtime:
|
|
|
911
1090
|
|
|
912
1091
|
Weak \`ETag\` + \`Cache-Control: private, no-cache\`. Send \`If-None-Match\` \u2192 304.
|
|
913
1092
|
Poll no faster than every 30s (the kit uses 60s). No push channel yet.
|
|
914
|
-
Flag values are not secrets
|
|
1093
|
+
Flag values are not secrets, but the endpoint is role-gated: only users with a
|
|
1094
|
+
recognised site role can read them (see Permission below); a signed-in user
|
|
1095
|
+
with no role gets 403.
|
|
915
1096
|
|
|
916
1097
|
### Shell federated app (browser)
|
|
917
1098
|
|
|
@@ -1026,8 +1207,9 @@ routes (omit it or use \`false\` otherwise \u2014 never \`null\`). \`visibility:
|
|
|
1026
1207
|
|
|
1027
1208
|
## suggest_cli
|
|
1028
1209
|
|
|
1210
|
+
- 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)
|
|
1029
1211
|
- init_app \u2192 \`gf apps init --slug <portal-slug>\`
|
|
1030
|
-
- publish_app \u2192 init + npm install + npm run release + gf apps publish --path release.zip
|
|
1212
|
+
- publish_app \u2192 create + init + npm install + npm run release + gf apps publish --path release.zip
|
|
1031
1213
|
`;
|
|
1032
1214
|
|
|
1033
1215
|
// src/vault-guide.ts
|
|
@@ -1074,14 +1256,63 @@ Example:
|
|
|
1074
1256
|
Or use \`gf api -X POST /v1/workspaces/{workspace_id}/vault/collections --body '{...}'\`.
|
|
1075
1257
|
|
|
1076
1258
|
\`GET \u2026/vault/collections\` lists schema-registered collections. Implicit
|
|
1077
|
-
tables created by the first \`POST \u2026/vault/{collection}\` appear when
|
|
1078
|
-
\`include_implicit=true\` (or after a schema create).
|
|
1259
|
+
tables created by the first \`POST \u2026/vault/collections/{collection}\` appear when
|
|
1260
|
+
you pass \`include_implicit=true\` (or after a schema create).
|
|
1261
|
+
|
|
1262
|
+
## Records: routes
|
|
1263
|
+
|
|
1264
|
+
All under \`/v1/workspaces/{workspace_id}/vault\`:
|
|
1079
1265
|
|
|
1080
|
-
|
|
1266
|
+
| Method and path | Purpose |
|
|
1267
|
+
|---|---|
|
|
1268
|
+
| \`POST /collections/{collection}/query\` | Filtered, paginated read |
|
|
1269
|
+
| \`GET /collections/{collection}/count\` | Count; optional \`filter\` query param (JSON object) |
|
|
1270
|
+
| \`POST /collections/{collection}\` | Create one record, body \`{"record": {...}}\`, 201, returns \`{"document": {...}}\` |
|
|
1271
|
+
| \`PUT /collections/{collection}/{document_id}\` | Update one record, body \`{"record": {...}}\`, returns \`{"document": {...}}\` |
|
|
1272
|
+
| \`DELETE /collections/{collection}/{document_id}\` | Delete one record, 204 |
|
|
1273
|
+
| \`GET /collections/{collection}/schema\` | Schema |
|
|
1274
|
+
| \`GET\`/\`PUT /collections/{collection}/acl\` | Collection ACL |
|
|
1081
1275
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1276
|
+
There is **no** \`/vault/{collection}\` route; always include \`/collections/\`.
|
|
1277
|
+
\`?connection=\` picks the database (default \`postgresql\`).
|
|
1278
|
+
|
|
1279
|
+
## Query: request and response
|
|
1280
|
+
|
|
1281
|
+
Request body for \`POST \u2026/collections/{collection}/query\` (all optional):
|
|
1282
|
+
\`filter\`, \`sort\` (e.g. \`["-created_at", "name"]\`), \`projection\`,
|
|
1283
|
+
\`limit\` (1-500, default 50), \`offset\` (default 0), \`connection\`.
|
|
1284
|
+
|
|
1285
|
+
Response:
|
|
1286
|
+
|
|
1287
|
+
\`\`\`json
|
|
1288
|
+
{ "items": [ {...} ], "total": 123, "has_more": true, "next_offset": 50 }
|
|
1289
|
+
\`\`\`
|
|
1290
|
+
|
|
1291
|
+
- The array key is **\`items\`** (not \`records\`).
|
|
1292
|
+
- \`total\` is the match count from Dataplane (see below for \`$text\`) and is
|
|
1293
|
+
**null** when upstream gave no count; it is never filled in from the page
|
|
1294
|
+
length. Do not treat \`total === items.length\` as the full table size.
|
|
1295
|
+
- Page with \`next_offset\` while \`has_more\` is true; \`next_offset\` is null
|
|
1296
|
+
on the last page.
|
|
1297
|
+
|
|
1298
|
+
## Substring search (\`$text\` / \`$search\`)
|
|
1299
|
+
|
|
1300
|
+
\`{ "filter": { "$text": "alice" } }\` is a case-insensitive substring match
|
|
1301
|
+
over every field value. Control Plane runs it itself by scanning the caller's
|
|
1302
|
+
ACL-filtered view, at most \`VAULT_TEXT_SEARCH_MAX_SCAN_ROWS\` rows per request
|
|
1303
|
+
(default 10 000).
|
|
1304
|
+
|
|
1305
|
+
- Plain query: for \`$text\`, \`total\` is computed by Control Plane (not
|
|
1306
|
+
Dataplane). It is exact when the collection fits under the cap; past the cap
|
|
1307
|
+
it is **null** and the results cover only the scanned rows.
|
|
1308
|
+
- Large collections: send \`"chunked_scan": true\`, then
|
|
1309
|
+
\`"scan_cursor": <scan.next_cursor>\` with the same filter/sort until
|
|
1310
|
+
\`scan.complete\` is true. Each response has a \`scan\` block
|
|
1311
|
+
(\`complete\`, \`next_cursor\`, \`scanned_rows\`, \`total_rows\`); the exact
|
|
1312
|
+
match total is the sum of \`items\` across chunks. The cursor is not a
|
|
1313
|
+
snapshot \u2014 concurrent writes can skip or repeat rows.
|
|
1314
|
+
- \`GET \u2026/count\` with \`$text\` uses the plain bounded scan and returns 400
|
|
1315
|
+
past the cap; use the chunked query instead.
|
|
1085
1316
|
|
|
1086
1317
|
## Filters
|
|
1087
1318
|
|
|
@@ -1114,6 +1345,17 @@ schedules.
|
|
|
1114
1345
|
|
|
1115
1346
|
Do not \`--env SENDGRID_SECRET=\u2026\`. That is **422**.
|
|
1116
1347
|
\`gf secrets set SENDGRID_SECRET\` then \`gf deploy --secret SENDGRID_SECRET\`.
|
|
1348
|
+
|
|
1349
|
+
**Caveat:** \`--secret\` injection needs the platform-wide Control Plane setting
|
|
1350
|
+
\`CODERUNNER_INJECT_ENV_SECRETS\`, which is **off by default** and not
|
|
1351
|
+
configurable per workspace. When it is off the secret is not injected. Fallback:
|
|
1352
|
+
give the Coderunner's M2M client the \`secrets:read\` scope (Portal
|
|
1353
|
+
**Administer \u2192 M2M tokens**) and read the value at runtime with
|
|
1354
|
+
\`GET /v1/workspaces/{workspace_id}/secrets/{key}\` using the M2M bearer token.
|
|
1355
|
+
It is read-only, live (no redeploy on rotation) and workspace-wide. Details in
|
|
1356
|
+
\`docs_get\` id \`coderunner\`. The deployed value of the setting cannot be
|
|
1357
|
+
checked from the repo; if \`--secret\` fails or the variable is empty, use the
|
|
1358
|
+
fallback rather than plaintext \`--env\`.
|
|
1117
1359
|
`.trim();
|
|
1118
1360
|
|
|
1119
1361
|
// src/version.ts
|
|
@@ -1149,7 +1391,7 @@ var DOC_CATALOG = [
|
|
|
1149
1391
|
path: "/docs/deploy",
|
|
1150
1392
|
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.",
|
|
1151
1393
|
embedded: CODERUNNER_GUIDE,
|
|
1152
|
-
lastUpdated: "2026-10-
|
|
1394
|
+
lastUpdated: "2026-10-08"
|
|
1153
1395
|
},
|
|
1154
1396
|
{
|
|
1155
1397
|
id: "coderunner-runtime",
|
|
@@ -1157,21 +1399,15 @@ var DOC_CATALOG = [
|
|
|
1157
1399
|
path: "/docs/deploy",
|
|
1158
1400
|
summary: "What the platform expects your code to do: Node handler.js for function/job/schedule, npm start + PORT/8080 + bind 0.0.0.0 for service/Dockerfile, Python bind.",
|
|
1159
1401
|
embedded: CODERUNNER_RUNTIME_CONTRACT,
|
|
1160
|
-
lastUpdated: "2026-10-
|
|
1161
|
-
},
|
|
1162
|
-
{
|
|
1163
|
-
id: "coderunner-overview",
|
|
1164
|
-
title: "Coderunner overview",
|
|
1165
|
-
path: "/docs/deploy",
|
|
1166
|
-
summary: "Architecture and lifecycle. Prefer docs_get id coderunner for type usage, Dockerfile, and create prerequisites.",
|
|
1167
|
-
embedded: CODERUNNER_GUIDE,
|
|
1168
|
-
lastUpdated: "2026-10-02"
|
|
1402
|
+
lastUpdated: "2026-10-08"
|
|
1169
1403
|
},
|
|
1170
1404
|
{
|
|
1171
1405
|
id: "authentication",
|
|
1172
1406
|
title: "Workspace Authentication",
|
|
1173
1407
|
path: "/docs/access/app-authentication",
|
|
1174
|
-
summary: "
|
|
1408
|
+
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. Optional; not required to create Apps or Coderunners. Separate from portal Team.",
|
|
1409
|
+
embedded: APP_AUTHENTICATION_GUIDE,
|
|
1410
|
+
lastUpdated: "2026-10-08"
|
|
1175
1411
|
},
|
|
1176
1412
|
{
|
|
1177
1413
|
id: "access",
|
|
@@ -1197,7 +1433,7 @@ var DOC_CATALOG = [
|
|
|
1197
1433
|
path: "/docs/apps/shell",
|
|
1198
1434
|
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.",
|
|
1199
1435
|
embedded: SHELL_APP_GUIDE,
|
|
1200
|
-
lastUpdated: "2026-
|
|
1436
|
+
lastUpdated: "2026-10-08"
|
|
1201
1437
|
},
|
|
1202
1438
|
{
|
|
1203
1439
|
id: "design-standard",
|
|
@@ -1227,9 +1463,9 @@ var DOC_CATALOG = [
|
|
|
1227
1463
|
id: "vault",
|
|
1228
1464
|
title: "Data Vault",
|
|
1229
1465
|
path: "/docs/api/vault",
|
|
1230
|
-
summary: "Create collections with JSON Schema, query
|
|
1466
|
+
summary: "Create collections with JSON Schema, record routes (/vault/collections/{collection}/\u2026), query request/response (items, total, has_more, next_offset, limit 1-500), $text substring search (bounded scan, chunked_scan/scan_cursor), ACLs, implicit tables, nested filters, identities vs M2M, secrets fallback.",
|
|
1231
1467
|
embedded: VAULT_GUIDE,
|
|
1232
|
-
lastUpdated: "2026-09
|
|
1468
|
+
lastUpdated: "2026-10-09"
|
|
1233
1469
|
},
|
|
1234
1470
|
{
|
|
1235
1471
|
id: "feature-flags",
|
|
@@ -1237,7 +1473,7 @@ var DOC_CATALOG = [
|
|
|
1237
1473
|
path: "/docs/configure/feature-flags",
|
|
1238
1474
|
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.",
|
|
1239
1475
|
embedded: FLAGS_GUIDE,
|
|
1240
|
-
lastUpdated: "2026-
|
|
1476
|
+
lastUpdated: "2026-10-08"
|
|
1241
1477
|
},
|
|
1242
1478
|
{
|
|
1243
1479
|
id: "flags-api",
|
|
@@ -1245,7 +1481,7 @@ var DOC_CATALOG = [
|
|
|
1245
1481
|
path: "/docs/api/flags",
|
|
1246
1482
|
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.",
|
|
1247
1483
|
embedded: FLAGS_GUIDE,
|
|
1248
|
-
lastUpdated: "2026-
|
|
1484
|
+
lastUpdated: "2026-10-08"
|
|
1249
1485
|
},
|
|
1250
1486
|
{
|
|
1251
1487
|
id: "scheduled-jobs",
|
|
@@ -1253,12 +1489,19 @@ var DOC_CATALOG = [
|
|
|
1253
1489
|
path: "/docs/deploy/schedules",
|
|
1254
1490
|
summary: "workload_type=schedule plus cron. gf coderunner status shows type/schedule/last_job. Use Process Log, not a public URL.",
|
|
1255
1491
|
embedded: CODERUNNER_GUIDE,
|
|
1256
|
-
lastUpdated: "2026-10-
|
|
1492
|
+
lastUpdated: "2026-10-08"
|
|
1257
1493
|
}
|
|
1258
1494
|
];
|
|
1259
1495
|
function docUrl(entry) {
|
|
1260
1496
|
return `${DOCS_ORIGIN}${entry.path}`;
|
|
1261
1497
|
}
|
|
1498
|
+
var DOC_ALIASES = {
|
|
1499
|
+
"coderunner-overview": "coderunner"
|
|
1500
|
+
};
|
|
1501
|
+
function getDocById(id) {
|
|
1502
|
+
const key = DOC_ALIASES[id] ?? id;
|
|
1503
|
+
return DOC_CATALOG.find((d) => d.id === key);
|
|
1504
|
+
}
|
|
1262
1505
|
function findDoc(query) {
|
|
1263
1506
|
const q = query.trim().toLowerCase();
|
|
1264
1507
|
if (!q) return DOC_CATALOG;
|
|
@@ -1272,7 +1515,7 @@ function htmlToText(html) {
|
|
|
1272
1515
|
return article.replace(/<script\b[\s\S]*?<\/script>/gi, " ").replace(/<style\b[\s\S]*?<\/style>/gi, " ").replace(/<nav\b[\s\S]*?<\/nav>/gi, " ").replace(/<footer\b[\s\S]*?<\/footer>/gi, " ").replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/'/g, "'").replace(/"/g, '"').replace(/\s+/g, " ").trim();
|
|
1273
1516
|
}
|
|
1274
1517
|
var MAX_CHARS = 12e3;
|
|
1275
|
-
async function fetchDocPage(entry) {
|
|
1518
|
+
async function fetchDocPage(entry, offset = 0) {
|
|
1276
1519
|
const url = docUrl(entry);
|
|
1277
1520
|
if (entry.embedded?.trim()) {
|
|
1278
1521
|
return {
|
|
@@ -1293,15 +1536,18 @@ async function fetchDocPage(entry) {
|
|
|
1293
1536
|
throw new Error(`Docs fetch failed: ${res.status} ${url}`);
|
|
1294
1537
|
}
|
|
1295
1538
|
const html = await res.text();
|
|
1296
|
-
|
|
1297
|
-
const
|
|
1298
|
-
|
|
1299
|
-
|
|
1539
|
+
const full = htmlToText(html);
|
|
1540
|
+
const start = Math.max(0, Math.min(offset, full.length));
|
|
1541
|
+
const end = start + MAX_CHARS;
|
|
1542
|
+
const truncated = full.length > end;
|
|
1543
|
+
const body = full.slice(start, end) + (truncated ? "\n\u2026" : "");
|
|
1300
1544
|
return {
|
|
1301
1545
|
url,
|
|
1302
1546
|
title: entry.title,
|
|
1303
1547
|
text: body,
|
|
1304
1548
|
truncated,
|
|
1549
|
+
totalChars: full.length,
|
|
1550
|
+
nextOffset: truncated ? end : null,
|
|
1305
1551
|
source: "docs.groundfloor.cloud",
|
|
1306
1552
|
lastUpdated: null,
|
|
1307
1553
|
mcpVersion: GF_MCP_VERSION
|
|
@@ -1309,6 +1555,10 @@ async function fetchDocPage(entry) {
|
|
|
1309
1555
|
}
|
|
1310
1556
|
|
|
1311
1557
|
// src/playbook.ts
|
|
1558
|
+
function createAppCommand(slug, name) {
|
|
1559
|
+
const displayName = name?.trim() || "<App Name>";
|
|
1560
|
+
return `gf apps create --name ${JSON.stringify(displayName)} --slug ${slug || "<portal-slug>"} --kind shell_federated`;
|
|
1561
|
+
}
|
|
1312
1562
|
function suggestCli(intent, args) {
|
|
1313
1563
|
const ws = args?.workspace ? ` -w ${args.workspace}` : "";
|
|
1314
1564
|
const name = args?.name ?? args?.coderunner;
|
|
@@ -1349,6 +1599,14 @@ function suggestCli(intent, args) {
|
|
|
1349
1599
|
why: "Process Log (stdout/stderr). gf deploy prints the last lines on failure; this command fetches more. runner_id accepts a coderunner id.",
|
|
1350
1600
|
docs: "https://docs.groundfloor.cloud/docs/deploy"
|
|
1351
1601
|
};
|
|
1602
|
+
case "create_app": {
|
|
1603
|
+
const slug = args?.slug?.trim();
|
|
1604
|
+
return {
|
|
1605
|
+
command: createAppCommand(slug, args?.name),
|
|
1606
|
+
why: 'Register the App in the workspace. gf apps publish resolves the App by slug and fails with "App not found" if this was never run. A 409 means the slug already exists: skip to init/publish. This is a Shell federated App, not a Coderunner (never --kind coderunner).',
|
|
1607
|
+
docs: "https://docs.groundfloor.cloud/docs/apps/shell/getting-started"
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1352
1610
|
case "init_app": {
|
|
1353
1611
|
const slug = args?.slug?.trim();
|
|
1354
1612
|
return {
|
|
@@ -1357,12 +1615,15 @@ function suggestCli(intent, args) {
|
|
|
1357
1615
|
docs: "https://docs.groundfloor.cloud/docs/apps/shell"
|
|
1358
1616
|
};
|
|
1359
1617
|
}
|
|
1360
|
-
case "publish_app":
|
|
1618
|
+
case "publish_app": {
|
|
1619
|
+
const slug = args?.slug?.trim();
|
|
1620
|
+
const dir = slug || "<slug>";
|
|
1361
1621
|
return {
|
|
1362
|
-
command: args?.
|
|
1363
|
-
why: "Ship a Shell federated App.
|
|
1622
|
+
command: `${createAppCommand(slug, args?.name)} && gf apps init --slug ${slug || "<portal-slug>"} && cd ${dir} && npm install && npm run release && gf apps publish --path release.zip`,
|
|
1623
|
+
why: "Ship a Shell federated App. Register it (gf apps create), then init the kit. Skip create if the App already exists (create returns 409). npm run release (not zip dist/, not a lone remoteEntry.js). Do not gf deploy. Do not invent App.tsx.",
|
|
1364
1624
|
docs: "https://docs.groundfloor.cloud/docs/apps/shell"
|
|
1365
1625
|
};
|
|
1626
|
+
}
|
|
1366
1627
|
case "environments":
|
|
1367
1628
|
return {
|
|
1368
1629
|
command: `gf environments ls${ws}`,
|
|
@@ -1371,8 +1632,8 @@ function suggestCli(intent, args) {
|
|
|
1371
1632
|
};
|
|
1372
1633
|
case "cancel_deployment":
|
|
1373
1634
|
return {
|
|
1374
|
-
command: name ? `gf coderunner cancel-deployment -c ${name}${ws}` : `gf coderunner cancel-deployment -c <id-or-slug
|
|
1375
|
-
why: "Delete the current deployment (or --deployment-id). Use this for a deploy stuck in deploying.",
|
|
1635
|
+
command: name ? `gf coderunner cancel-deployment -c ${name} --yes${ws}` : `gf coderunner cancel-deployment -c <id-or-slug> --yes${ws}`,
|
|
1636
|
+
why: "Delete the current deployment (or --deployment-id). Use this for a deploy stuck in deploying. --yes cancels without a confirmation prompt (required without a TTY).",
|
|
1376
1637
|
docs: "https://docs.groundfloor.cloud/docs/deploy"
|
|
1377
1638
|
};
|
|
1378
1639
|
case "secrets":
|
|
@@ -1403,7 +1664,7 @@ function suggestCli(intent, args) {
|
|
|
1403
1664
|
default:
|
|
1404
1665
|
return {
|
|
1405
1666
|
command: "gf --help",
|
|
1406
|
-
why: `Unknown intent "${intent}". Use login | switch_workspace | deploy | run | status | logs | environments | cancel_deployment | init_app | publish_app | secrets | flags | dataplane | vault_token.`,
|
|
1667
|
+
why: `Unknown intent "${intent}". Use login | switch_workspace | deploy | run | status | logs | environments | cancel_deployment | create_app | init_app | publish_app | secrets | flags | dataplane | vault_token.`,
|
|
1407
1668
|
docs: "https://docs.groundfloor.cloud/docs/developer-tools/cli"
|
|
1408
1669
|
};
|
|
1409
1670
|
}
|
|
@@ -1434,7 +1695,9 @@ function errText(err) {
|
|
|
1434
1695
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
1435
1696
|
}
|
|
1436
1697
|
|
|
1437
|
-
// src/
|
|
1698
|
+
// src/server.ts
|
|
1699
|
+
var READ_ONLY = { readOnlyHint: true };
|
|
1700
|
+
var DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true };
|
|
1438
1701
|
var wsId = z.string().optional().describe("Override the gf default workspace for this call only");
|
|
1439
1702
|
async function cfg(workspaceId, requireWorkspace = true) {
|
|
1440
1703
|
return resolveMcpConfig({ workspaceId, requireWorkspace });
|
|
@@ -1453,6 +1716,7 @@ server.tool(
|
|
|
1453
1716
|
"whoami",
|
|
1454
1717
|
"Show the gf CLI session (identity, cell, selected workspace). Run `gf login` if this fails.",
|
|
1455
1718
|
{},
|
|
1719
|
+
READ_ONLY,
|
|
1456
1720
|
async () => {
|
|
1457
1721
|
try {
|
|
1458
1722
|
const c = await cfg(void 0, false);
|
|
@@ -1476,6 +1740,7 @@ server.tool(
|
|
|
1476
1740
|
"workspace_context",
|
|
1477
1741
|
"Live context for the selected workspace: session, coderunners, apps, environments (with the default one), feature flags, dataplane (access layer, not a database), databases_howto, suggested gf commands. Use docs_get id coderunner before deploying; id databases for data work; id feature-flags for toggles/config; id css-and-styling for own CSS framework.",
|
|
1478
1742
|
{ workspace_id: wsId },
|
|
1743
|
+
READ_ONLY,
|
|
1479
1744
|
async ({ workspace_id }) => {
|
|
1480
1745
|
try {
|
|
1481
1746
|
const c = await cfg(workspace_id);
|
|
@@ -1535,12 +1800,16 @@ server.tool(
|
|
|
1535
1800
|
node: "function/job/schedule without a Dockerfile run a generated /app/main.js that requires ./handler: ship a root handler.js exporting handler (scripts.start not needed). service/Dockerfile running npm start: scripts.start, listen PORT||8080, bind 0.0.0.0. gf deploy fails fast on either missing.",
|
|
1536
1801
|
local_live_data: 'eval "$(gf env)". For files/secrets as a builder use Bearer $(gf token). For Vault/Files as the deployed workload, attach an M2M client and use Bearer $(gf m2m token). Never DATAPLANE_SERVICE_API_KEY. Do not zip .env files.'
|
|
1537
1802
|
},
|
|
1803
|
+
app_end_users: {
|
|
1804
|
+
docs_get: "authentication",
|
|
1805
|
+
rule: "End users of a customer's app sign in only to that app, through the workspace's App Authentication realm (Administer \u2192 App authentication). They have no Groundfloor console account and no workspace membership; their site role (viewer/user/admin or custom) decides what they reach via Site access. Do not build or suggest Portal logins for them."
|
|
1806
|
+
},
|
|
1538
1807
|
shell_app_howto: {
|
|
1539
1808
|
docs_get: "shell",
|
|
1540
|
-
rule: "Never invent a Vite + Module Federation app. gf apps init --slug <portal-slug>, npm install, npm run release, gf apps publish --path release.zip. Do not zip dist/ or upload only remoteEntry.js.",
|
|
1809
|
+
rule: "Never invent a Vite + Module Federation app. gf apps create --name <name> --slug <portal-slug> --kind shell_federated (registers the App; publish fails with App not found without it), gf apps init --slug <portal-slug>, npm install, npm run release, gf apps publish --path release.zip. Do not zip dist/ or upload only remoteEntry.js.",
|
|
1541
1810
|
css: "Tailwind is the default (docs_get id css-and-styling). Own framework: FederatedStyleRoot + ?inline CSS in Shadow DOM. Do not load a second CSS framework onto the Shell page.",
|
|
1542
1811
|
design: "docs_get id design-standard",
|
|
1543
|
-
suggest_cli: "init_app | publish_app"
|
|
1812
|
+
suggest_cli: "create_app | init_app | publish_app"
|
|
1544
1813
|
},
|
|
1545
1814
|
databases_howto: {
|
|
1546
1815
|
docs_get: "databases",
|
|
@@ -1559,6 +1828,7 @@ server.tool(
|
|
|
1559
1828
|
flags: suggestCli("flags"),
|
|
1560
1829
|
cancel_deployment: suggestCli("cancel_deployment"),
|
|
1561
1830
|
vault_token: suggestCli("m2m_token"),
|
|
1831
|
+
create_app: suggestCli("create_app"),
|
|
1562
1832
|
init_app: suggestCli("init_app"),
|
|
1563
1833
|
publish_app: suggestCli("publish_app")
|
|
1564
1834
|
},
|
|
@@ -1578,6 +1848,7 @@ server.tool(
|
|
|
1578
1848
|
"workspaces_list",
|
|
1579
1849
|
"List workspaces visible to the current user. To change the default, tell the user to run `gf workspaces use <uuid>` (CLI owns that).",
|
|
1580
1850
|
{},
|
|
1851
|
+
READ_ONLY,
|
|
1581
1852
|
async () => {
|
|
1582
1853
|
try {
|
|
1583
1854
|
const c = await cfg(void 0, false);
|
|
@@ -1599,6 +1870,7 @@ server.tool(
|
|
|
1599
1870
|
"coderunner_list",
|
|
1600
1871
|
"List Coderunners in the workspace (read-only).",
|
|
1601
1872
|
{ workspace_id: wsId },
|
|
1873
|
+
READ_ONLY,
|
|
1602
1874
|
async ({ workspace_id }) => {
|
|
1603
1875
|
try {
|
|
1604
1876
|
const c = await cfg(workspace_id);
|
|
@@ -1616,6 +1888,7 @@ server.tool(
|
|
|
1616
1888
|
workspace_id: wsId,
|
|
1617
1889
|
coderunner_id: z.string().min(1).describe("Coderunner id or slug")
|
|
1618
1890
|
},
|
|
1891
|
+
READ_ONLY,
|
|
1619
1892
|
async (args) => {
|
|
1620
1893
|
try {
|
|
1621
1894
|
const c = await cfg(args.workspace_id);
|
|
@@ -1645,6 +1918,7 @@ server.tool(
|
|
|
1645
1918
|
search: z.string().optional(),
|
|
1646
1919
|
deployment_id: z.string().optional()
|
|
1647
1920
|
},
|
|
1921
|
+
READ_ONLY,
|
|
1648
1922
|
async (args) => {
|
|
1649
1923
|
try {
|
|
1650
1924
|
const c = await cfg(args.workspace_id);
|
|
@@ -1671,6 +1945,7 @@ server.tool(
|
|
|
1671
1945
|
"environments_list",
|
|
1672
1946
|
"List environments in the workspace (id, name, status).",
|
|
1673
1947
|
{ workspace_id: wsId },
|
|
1948
|
+
READ_ONLY,
|
|
1674
1949
|
async ({ workspace_id }) => {
|
|
1675
1950
|
try {
|
|
1676
1951
|
const c = await cfg(workspace_id);
|
|
@@ -1686,30 +1961,23 @@ server.tool(
|
|
|
1686
1961
|
);
|
|
1687
1962
|
server.tool(
|
|
1688
1963
|
"coderunner_cancel_deployment",
|
|
1689
|
-
"
|
|
1964
|
+
"DESTRUCTIVE. Deletes one deployment of a Coderunner; if it is the live deployment the service goes down. deployment_id is required (find it with coderunner_get_status). Acts only on the configured workspace (gf workspaces use). Same as `gf coderunner cancel-deployment --deployment-id`. Confirm with the user before calling.",
|
|
1690
1965
|
{
|
|
1691
|
-
workspace_id: wsId,
|
|
1692
1966
|
coderunner_id: z.string().min(1).describe("Coderunner id or slug"),
|
|
1693
|
-
deployment_id: z.string().
|
|
1967
|
+
deployment_id: z.string().trim().min(1).describe(
|
|
1968
|
+
"Id of the deployment to delete (coderunner_get_status \u2192 deployment.id). Required: there is no default."
|
|
1969
|
+
)
|
|
1694
1970
|
},
|
|
1971
|
+
DESTRUCTIVE,
|
|
1695
1972
|
async (args) => {
|
|
1696
1973
|
try {
|
|
1697
|
-
const c = await cfg(
|
|
1974
|
+
const c = await cfg();
|
|
1698
1975
|
const id = await resolveCoderunnerId(
|
|
1699
1976
|
c.apiUrl,
|
|
1700
1977
|
c.workspaceId,
|
|
1701
1978
|
args.coderunner_id
|
|
1702
1979
|
);
|
|
1703
|
-
|
|
1704
|
-
if (!deploymentId) {
|
|
1705
|
-
const current = await getDeployment(c.apiUrl, c.workspaceId, id);
|
|
1706
|
-
deploymentId = current?.id?.trim();
|
|
1707
|
-
}
|
|
1708
|
-
if (!deploymentId) {
|
|
1709
|
-
throw new Error(
|
|
1710
|
-
`No current deployment for coderunner ${id}. Pass deployment_id.`
|
|
1711
|
-
);
|
|
1712
|
-
}
|
|
1980
|
+
const deploymentId = args.deployment_id;
|
|
1713
1981
|
await deleteCoderunnerDeployment(
|
|
1714
1982
|
c.apiUrl,
|
|
1715
1983
|
c.workspaceId,
|
|
@@ -1717,6 +1985,7 @@ server.tool(
|
|
|
1717
1985
|
deploymentId
|
|
1718
1986
|
);
|
|
1719
1987
|
return text({
|
|
1988
|
+
workspace_id: c.workspaceId,
|
|
1720
1989
|
coderunner_id: id,
|
|
1721
1990
|
deployment_id: deploymentId,
|
|
1722
1991
|
deleted: true,
|
|
@@ -1735,6 +2004,7 @@ server.tool(
|
|
|
1735
2004
|
coderunner_id: z.string().min(1),
|
|
1736
2005
|
job_id: z.string().min(1)
|
|
1737
2006
|
},
|
|
2007
|
+
READ_ONLY,
|
|
1738
2008
|
async (args) => {
|
|
1739
2009
|
try {
|
|
1740
2010
|
const c = await cfg(args.workspace_id);
|
|
@@ -1753,6 +2023,7 @@ server.tool(
|
|
|
1753
2023
|
"apps_list",
|
|
1754
2024
|
"List product Apps (Shell / standalone). Workloads are Coderunners, not Apps.",
|
|
1755
2025
|
{ workspace_id: wsId },
|
|
2026
|
+
READ_ONLY,
|
|
1756
2027
|
async ({ workspace_id }) => {
|
|
1757
2028
|
try {
|
|
1758
2029
|
const c = await cfg(workspace_id);
|
|
@@ -1766,6 +2037,7 @@ server.tool(
|
|
|
1766
2037
|
"apps_get",
|
|
1767
2038
|
"Get one product App by id or slug.",
|
|
1768
2039
|
{ workspace_id: wsId, app_id: z.string().min(1) },
|
|
2040
|
+
READ_ONLY,
|
|
1769
2041
|
async (args) => {
|
|
1770
2042
|
try {
|
|
1771
2043
|
const c = await cfg(args.workspace_id);
|
|
@@ -1783,6 +2055,7 @@ server.tool(
|
|
|
1783
2055
|
"secrets_list",
|
|
1784
2056
|
"List secret keys (values are not shown). To set/delete, use `gf secrets`.",
|
|
1785
2057
|
{ workspace_id: wsId, environment_id: z.string().optional() },
|
|
2058
|
+
READ_ONLY,
|
|
1786
2059
|
async (args) => {
|
|
1787
2060
|
try {
|
|
1788
2061
|
const c = await cfg(args.workspace_id);
|
|
@@ -1798,6 +2071,7 @@ server.tool(
|
|
|
1798
2071
|
"flags_list",
|
|
1799
2072
|
"List feature flags and values in one environment (omit environment_id \u2192 workspace default, dev; response echoes the resolved id). To set/delete/promote, use `gf flags`. docs_get id feature-flags for runtime use.",
|
|
1800
2073
|
{ workspace_id: wsId, environment_id: z.string().optional() },
|
|
2074
|
+
READ_ONLY,
|
|
1801
2075
|
async (args) => {
|
|
1802
2076
|
try {
|
|
1803
2077
|
const c = await cfg(args.workspace_id);
|
|
@@ -1813,6 +2087,7 @@ server.tool(
|
|
|
1813
2087
|
"files_list",
|
|
1814
2088
|
"List workspace files (read-only). Upload/download with `gf files`.",
|
|
1815
2089
|
{ workspace_id: wsId },
|
|
2090
|
+
READ_ONLY,
|
|
1816
2091
|
async ({ workspace_id }) => {
|
|
1817
2092
|
try {
|
|
1818
2093
|
const c = await cfg(workspace_id);
|
|
@@ -1824,17 +2099,45 @@ server.tool(
|
|
|
1824
2099
|
);
|
|
1825
2100
|
server.tool(
|
|
1826
2101
|
"domains_list",
|
|
1827
|
-
|
|
1828
|
-
{
|
|
2102
|
+
'List custom domains of a standalone App (the service-backed kind). Pass app_id (id or slug) or coderunner_id (the App\'s primary Coderunner). Standalone Coderunners have no domains of their own: the Coderunner domain routes always return 400 "Custom domains are available on Apps" and that server message is surfaced if hit. Shell apps have none. Add/verify with `gf domains` only for an App-backed service.',
|
|
2103
|
+
{
|
|
2104
|
+
workspace_id: wsId,
|
|
2105
|
+
app_id: z.string().min(1).optional().describe("App id or slug"),
|
|
2106
|
+
coderunner_id: z.string().min(1).optional().describe("Coderunner id or slug; resolves to the App whose primary Coderunner it is")
|
|
2107
|
+
},
|
|
2108
|
+
READ_ONLY,
|
|
1829
2109
|
async (args) => {
|
|
1830
2110
|
try {
|
|
2111
|
+
if (!args.app_id && !args.coderunner_id) {
|
|
2112
|
+
throw new Error("Pass app_id (id or slug) or coderunner_id.");
|
|
2113
|
+
}
|
|
1831
2114
|
const c = await cfg(args.workspace_id);
|
|
1832
|
-
const
|
|
2115
|
+
const listed = await listApps(c.apiUrl, c.workspaceId);
|
|
2116
|
+
let match;
|
|
2117
|
+
if (args.app_id) {
|
|
2118
|
+
match = listed.apps.find(
|
|
2119
|
+
(app) => app.id === args.app_id || app.slug === args.app_id
|
|
2120
|
+
);
|
|
2121
|
+
if (!match) throw new Error(`App not found: ${args.app_id}`);
|
|
2122
|
+
} else {
|
|
2123
|
+
const crId = await resolveCoderunnerId(
|
|
2124
|
+
c.apiUrl,
|
|
2125
|
+
c.workspaceId,
|
|
2126
|
+
args.coderunner_id
|
|
2127
|
+
);
|
|
2128
|
+
match = listed.apps.find((app) => app.primary_coderunner_id === crId);
|
|
2129
|
+
if (!match) {
|
|
2130
|
+
throw new Error(
|
|
2131
|
+
`Coderunner ${crId} is not the primary Coderunner of any App, and standalone Coderunners cannot have custom domains. Package it as an App first (gf apps create --kind standalone --primary-coderunner <id>).`
|
|
2132
|
+
);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
const domains = await cpGetJson(
|
|
1833
2136
|
c.apiUrl,
|
|
1834
|
-
c.
|
|
1835
|
-
|
|
2137
|
+
c.token,
|
|
2138
|
+
`/v1/workspaces/${encodeURIComponent(c.workspaceId)}/apps/${encodeURIComponent(match.id)}/coderunner/domains`
|
|
1836
2139
|
);
|
|
1837
|
-
return text(
|
|
2140
|
+
return text({ app_id: match.id, app_slug: match.slug, ...domains });
|
|
1838
2141
|
} catch (e) {
|
|
1839
2142
|
return errText(e);
|
|
1840
2143
|
}
|
|
@@ -1842,8 +2145,9 @@ server.tool(
|
|
|
1842
2145
|
);
|
|
1843
2146
|
server.tool(
|
|
1844
2147
|
"dataplane_status",
|
|
1845
|
-
"Dataplane setup status
|
|
2148
|
+
"Dataplane setup status for the workspace, as reported by Control Plane. Provision with `gf dataplane provision`. See docs_get id databases for what Dataplane is.",
|
|
1846
2149
|
{ workspace_id: wsId },
|
|
2150
|
+
READ_ONLY,
|
|
1847
2151
|
async ({ workspace_id }) => {
|
|
1848
2152
|
try {
|
|
1849
2153
|
const c = await cfg(workspace_id);
|
|
@@ -1853,10 +2157,73 @@ server.tool(
|
|
|
1853
2157
|
}
|
|
1854
2158
|
}
|
|
1855
2159
|
);
|
|
2160
|
+
var accountId = z.string().optional().describe(
|
|
2161
|
+
"Organization/account id (NOT the workspace id). Defaults to the account that owns the selected workspace."
|
|
2162
|
+
);
|
|
2163
|
+
async function resolveAccountId(c, explicit) {
|
|
2164
|
+
if (explicit?.trim()) return explicit.trim();
|
|
2165
|
+
const workspaces = await listWorkspaces(c.apiUrl);
|
|
2166
|
+
const ws = workspaces.find((w) => w.id === c.workspaceId);
|
|
2167
|
+
const id = ws?.account_id || ws?.organization_id;
|
|
2168
|
+
if (!id) {
|
|
2169
|
+
throw new Error(
|
|
2170
|
+
"Could not determine the account for the selected workspace. Pass account_id (organization id)."
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
2173
|
+
return id;
|
|
2174
|
+
}
|
|
2175
|
+
server.tool(
|
|
2176
|
+
"quota_get",
|
|
2177
|
+
"Plan tier and usage against plan limits (workspaces, file storage, activity-log events, \u2026) for the organization that owns the workspace. Account-scoped, not workspace-scoped; readable by organization members. Use when a create/deploy is refused for plan reasons.",
|
|
2178
|
+
{ workspace_id: wsId, account_id: accountId },
|
|
2179
|
+
READ_ONLY,
|
|
2180
|
+
async (args) => {
|
|
2181
|
+
try {
|
|
2182
|
+
const c = await cfg(args.workspace_id);
|
|
2183
|
+
const id = await resolveAccountId(c, args.account_id);
|
|
2184
|
+
return text(
|
|
2185
|
+
await cpGetJson(
|
|
2186
|
+
c.apiUrl,
|
|
2187
|
+
c.token,
|
|
2188
|
+
`/v1/accounts/${encodeURIComponent(id)}/quota`
|
|
2189
|
+
)
|
|
2190
|
+
);
|
|
2191
|
+
} catch (e) {
|
|
2192
|
+
return errText(e);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
);
|
|
2196
|
+
server.tool(
|
|
2197
|
+
"billing_summary",
|
|
2198
|
+
"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.",
|
|
2199
|
+
{
|
|
2200
|
+
workspace_id: wsId,
|
|
2201
|
+
account_id: accountId,
|
|
2202
|
+
since_seconds: z.number().int().min(3600).max(400 * 86400).optional().describe("Lookback window in seconds (server default 30 days; min 1 hour, max ~13 months)")
|
|
2203
|
+
},
|
|
2204
|
+
READ_ONLY,
|
|
2205
|
+
async (args) => {
|
|
2206
|
+
try {
|
|
2207
|
+
const c = await cfg(args.workspace_id);
|
|
2208
|
+
const id = await resolveAccountId(c, args.account_id);
|
|
2209
|
+
const qs = args.since_seconds ? `?since_seconds=${args.since_seconds}` : "";
|
|
2210
|
+
return text(
|
|
2211
|
+
await cpGetJson(
|
|
2212
|
+
c.apiUrl,
|
|
2213
|
+
c.token,
|
|
2214
|
+
`/v1/accounts/${encodeURIComponent(id)}/billing/summary${qs}`
|
|
2215
|
+
)
|
|
2216
|
+
);
|
|
2217
|
+
} catch (e) {
|
|
2218
|
+
return errText(e);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
);
|
|
1856
2222
|
server.tool(
|
|
1857
2223
|
"docs_list",
|
|
1858
2224
|
"Catalog of Groundfloor product docs (docs.groundfloor.cloud) the MCP can fetch.",
|
|
1859
2225
|
{ query: z.string().optional() },
|
|
2226
|
+
READ_ONLY,
|
|
1860
2227
|
async ({ query }) => {
|
|
1861
2228
|
const hits = findDoc(query ?? "");
|
|
1862
2229
|
return text({
|
|
@@ -1874,15 +2241,19 @@ server.tool(
|
|
|
1874
2241
|
);
|
|
1875
2242
|
server.tool(
|
|
1876
2243
|
"docs_get",
|
|
1877
|
-
"How-to for Coderunner, Shell, design-standard, css-and-styling, databases, access, m2m. 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.",
|
|
2244
|
+
"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.",
|
|
1878
2245
|
{
|
|
1879
2246
|
id: z.string().min(1).describe(
|
|
1880
2247
|
"Catalog id, e.g. coderunner | coderunner-runtime | shell | design-standard | css-and-styling | databases | agents | developers | authentication | access | m2m"
|
|
2248
|
+
),
|
|
2249
|
+
offset: z.number().int().min(0).optional().describe(
|
|
2250
|
+
"Live (non-embedded) pages only: character offset to resume from. Pass nextOffset from the previous result when truncated is true."
|
|
1881
2251
|
)
|
|
1882
2252
|
},
|
|
1883
|
-
|
|
2253
|
+
READ_ONLY,
|
|
2254
|
+
async ({ id, offset }) => {
|
|
1884
2255
|
try {
|
|
1885
|
-
const entry =
|
|
2256
|
+
const entry = getDocById(id);
|
|
1886
2257
|
if (!entry) {
|
|
1887
2258
|
return errText(
|
|
1888
2259
|
new Error(
|
|
@@ -1890,7 +2261,7 @@ server.tool(
|
|
|
1890
2261
|
)
|
|
1891
2262
|
);
|
|
1892
2263
|
}
|
|
1893
|
-
return text(await fetchDocPage(entry));
|
|
2264
|
+
return text(await fetchDocPage(entry, offset ?? 0));
|
|
1894
2265
|
} catch (e) {
|
|
1895
2266
|
return errText(e);
|
|
1896
2267
|
}
|
|
@@ -1898,7 +2269,7 @@ server.tool(
|
|
|
1898
2269
|
);
|
|
1899
2270
|
server.tool(
|
|
1900
2271
|
"suggest_cli",
|
|
1901
|
-
"Return the gf command to run. For a Shell App use intent init_app or publish_app (never scaffold App.tsx; never gf deploy). MCP does not execute mutations.",
|
|
2272
|
+
"Return the gf command to run. For a Shell App use intent create_app, init_app or publish_app (publish_app is the full create + init + release + publish chain; never scaffold App.tsx; never gf deploy). MCP does not execute mutations.",
|
|
1902
2273
|
{
|
|
1903
2274
|
intent: z.enum([
|
|
1904
2275
|
"login",
|
|
@@ -1909,6 +2280,7 @@ server.tool(
|
|
|
1909
2280
|
"logs",
|
|
1910
2281
|
"environments",
|
|
1911
2282
|
"cancel_deployment",
|
|
2283
|
+
"create_app",
|
|
1912
2284
|
"init_app",
|
|
1913
2285
|
"publish_app",
|
|
1914
2286
|
"secrets",
|
|
@@ -1918,10 +2290,11 @@ server.tool(
|
|
|
1918
2290
|
"m2m_token"
|
|
1919
2291
|
]),
|
|
1920
2292
|
workspace: z.string().optional(),
|
|
1921
|
-
name: z.string().optional().describe("Coderunner name/id for deploy/run/status/logs/cancel_deployment/vault_token"),
|
|
1922
|
-
slug: z.string().optional().describe("Portal app slug for init_app / publish_app"),
|
|
2293
|
+
name: z.string().optional().describe("Coderunner name/id for deploy/run/status/logs/cancel_deployment/vault_token; App display name for create_app / publish_app"),
|
|
2294
|
+
slug: z.string().optional().describe("Portal app slug for create_app / init_app / publish_app"),
|
|
1923
2295
|
cell: z.enum(["production", "stage", "dev"]).optional()
|
|
1924
2296
|
},
|
|
2297
|
+
READ_ONLY,
|
|
1925
2298
|
async (args) => {
|
|
1926
2299
|
return text(
|
|
1927
2300
|
suggestCli(args.intent, {
|
|
@@ -1934,6 +2307,8 @@ server.tool(
|
|
|
1934
2307
|
);
|
|
1935
2308
|
}
|
|
1936
2309
|
);
|
|
2310
|
+
|
|
2311
|
+
// src/index.ts
|
|
1937
2312
|
async function main() {
|
|
1938
2313
|
const transport = new StdioServerTransport();
|
|
1939
2314
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@groundfloorcloud/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Groundfloor MCP — inspect workspace state and docs; take actions with the gf CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "tsup",
|
|
29
29
|
"dev": "tsup --watch",
|
|
30
|
-
"typecheck": "
|
|
30
|
+
"typecheck": "tsc --noEmit",
|
|
31
31
|
"start": "node dist/index.js",
|
|
32
32
|
"clean": "rm -rf dist *.tsbuildinfo .turbo",
|
|
33
33
|
"prepublishOnly": "npm run build"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
37
|
-
"zod": "^
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.23.0",
|
|
37
|
+
"zod": "^4.4.3"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^20",
|