Skip to content

MCP tools

Reference

Greenlight exposes its functionality to coding agents as a fixed catalog of MCP tools. This page is the canonical reference: every tool, what it takes, what it returns, and a small example.

The conceptual model is in The agent protocol. This page assumes you know what MCP is and how an agent calls a tool.

Every tool is served by the greenlight MCP server and grouped by area. Jump to a group:

Area Tools
App lifecycle registerApp · getRepoAccess · getApp · listApps · addCoOwner · removeCoOwner
Marketplace discoverApps
Context (Knowledge) knowledgeList · knowledgeGet · knowledgeSearch · knowledgeAssetList · knowledgeAssetGet · knowledgePropose
Policy getPolicies
Integrations listGrantableIntegrations · requestCredentialAccess · requestIntegrationConnection · getPermissions
Environment variables envList · envSet · envRemove
Local development approveCliSession · inspectAppDb · inspectIntegrationApi · inspectAppBlob
Pipeline & operations createPullRequest · mergePullRequest · getPipelineRun · getLogs · getMetrics · getMetricsSeries · getAppDiagnostics · curlApp · getAppPreviewUrl
  • Tool names are flat camelCase (no dots) and served by the greenlight MCP server. Related tools share a name prefix (e.g. env*, knowledge*, inspect*).
  • Authorization is bound to the user the agent session is authenticated as. The agent cannot do anything the user couldn’t.
  • Environment — Each app has one environment. Tools that accept env default to shared when omitted.
  • Infrastructure is declarative. Workloads, resources (Postgres, blob), and integration grants are declared in greenlight.yml and applied by the merge-time reconciler — there is no MCP tool that provisions them. The catalog covers app lifecycle, env-var values, the pull-request flow, local development, and observability; the declared shape is read back through getApp.
  • Env writes take effect on deploy. envSet / envRemove store the value and return status: "complete"; the running app picks up the change on its next deploy.
  • No credential ever leaves Greenlight. No tool returns an upstream credential, a vault value, or a proxy/AI key. Tools that touch real data either run inside the control plane and return only results (inspect*), or deliver values to the laptop through the paired greenlight CLI — never through an MCP response.
  • Errors follow the error envelope. code is stable; messages are not.
  • Idempotency is supported on mutating tools that accept an idempotency_key. Repeated calls with the same key return the original response.
  • Pagination is cursor-based; see Errors & pagination.

Creates a new app as a scaffold — no cloud provisioning. It writes the apps row, the single shared environment row, and the SCM repository seeded with .gitignore, the Greenlight pipeline workflow, and a greenlight.yml carrying the app’s app_id, a docs.* block, and commented-out scaffold for workloads, resources, grants, and env. No Dockerfile is seeded — the agent authors it for the app’s stack in the first code PR. Workloads, resources, grants, and all infrastructure come from the first PR that declares them in greenlight.yml and merges. Registration is resumable and idempotent: calling it twice for the same slug resumes rather than creating a second app.

Input

{
"name": "Expense Tracker",
"slug": "expense-tracker",
"type": "server",
"description": "Quick reimbursement tracker for the finance team."
}

Output

{
"app_id": "app_k9x2m3p",
"slug": "expense-tracker",
"repo_full_name": "contoso/gl-app-expense-tracker",
"clone_url": "https://github.com/contoso/gl-app-expense-tracker",
"token_expires_at": "2026-03-12T15:08:11Z",
"registration_state": "complete",
"resumed": false
}

The way back into an existing project. Mints a fresh one-hour SCM token scoped to the app’s repo, so the agent can clone or pull. Called at the start of any session that needs to push code to an already-registered app, and again mid-session if the token is about to expire.

Input

{ "app_id": "app_k9x2m3p" }

Output

{
"clone_url": "https://github.com/contoso/gl-app-expense-tracker",
"token": "ghs_…",
"clone_command": "git clone https://x-access-token:ghs_…@github.com/contoso/gl-app-expense-tracker.git",
"authenticated_clone_url": "https://x-access-token:ghs_…@github.com/contoso/gl-app-expense-tracker.git",
"expires_at": "2026-03-12T16:08:11Z",
"repo_name": "gl-app-expense-tracker",
"repo_full_name": "contoso/gl-app-expense-tracker"
}

The token is a GitHub App installation token: it authenticates as the x-access-token user spliced into the HTTPS URL. clone_command and authenticated_clone_url are ready-to-run forms — the agent clones and pushes with those (or splices token into clone_url itself) rather than the gh CLI, which the agent’s session is usually not authenticated for.

Returns the current state of an app: the app row, its shared-environment summary, the declared manifest (resources, workloads, grants, env) projected from the last-merge SHA, per-grant approval status, the full env contract (user + managed names), and the latest pipeline run. Returns identifiers and metadata only — never raw manifest YAML, never env-var values.

Lists the caller’s apps, most recently deployed first (never-deployed apps last) — a citizen developer sees the apps they own or co-own; IT admin roles see every app in the organization. Cursor-paginated (limit 1–100, default 20).

Add or remove a co-owner on an app — used when a citizen developer says “let Bob help me with this.” Both take app_id, user_email, and an audit reason. The target must have logged into Greenlight at least once; otherwise addCoOwner returns user.not_found with the org login URL in next_steps.

The agent-facing counterpart to the dashboard Marketplace tab — browse the live apps in the org the caller can open, not just the ones they own or edit. Cursor-paginated (limit 1–100, default 20), with optional slug (exact) and name_query (substring) filters.

Every returned row is one the caller can open, so each carries its deployment_url. An app its owner has restricted to a named set of people is absent for everyone else rather than returned as a redacted row — there is nothing to request, so a row nobody can act on would only disclose that the app exists. Access is granted by the app’s owner naming a person in the dashboard.

Input

{ "name_query": "expense" }

Output

{
"items": [
{
"app_id": "app_k9x2m3p",
"slug": "expense-tracker",
"name": "Expense Tracker",
"owner_email": "avery@contoso.com",
"owner_name": "Avery Chen",
"deployment_url": "https://expense-tracker.apps.contoso.com",
"viewer_has_access": true
}
],
"next_cursor": null
}

Resources (Postgres, blob) and workloads (the app’s web server) are declared in greenlight.yml, not provisioned through MCP. The agent edits the resources: and workloads: blocks, opens a PR, and on merge the reconciler provisions the resources, renders the Kubernetes manifests, and rolls out the deploy. There is no resource.add tool; the declared and live state is read back through getApp, and merge-time provisioning outcomes surface through getPipelineRun.

Lists entry summaries for a scope.

Input

{
"scope": "org" | "integration" | "app",
"integration": "snowflake",
"app_id": "app_k9x2m3p",
"tags": ["data-modeling"],
"cursor": "..."
}

Output

{
"items": [
{ "id": "kn_pf2", "scope": "app", "topic": "architecture", "title": "App architecture", "version": 4, "updated_at": "2026-03-12T14:08:11Z" }
],
"next_cursor": null
}

Catalog and Custom integrations both start with no integration Knowledge; your team authors entries as it learns the source. If IT makes an integration inactive, every Knowledge tool — including the asset tools — hides or rejects its integration-scope entries and their assets for every agent role, while the preserved content remains editable in the dashboard.

Returns one entry’s full body.

Input

{ "id": "kn_pf2" }

or

{ "scope": "app", "app_id": "app_k9x2m3p", "topic": "architecture" }

Output

{
"id": "kn_pf2",
"scope": "app",
"topic": "architecture",
"title": "App architecture",
"body_md": "## Overview\n\nThis app...",
"version": 4,
"updated_at": "2026-03-12T14:08:11Z",
"last_editor_kind": "agent",
"assets": []
}

assets carries the metadata for any image files attached to the entry. body_md keeps gl-asset:<slug> references literal — fetch bytes with knowledgeAssetGet.

Full-text search over titles and bodies.

Input

{ "query": "snowflake warehouse", "scope": "integration" }

Output

{
"items": [
{ "id": "kn_a1", "title": "Snowflake warehouses we use", "scope": "integration", "snippet": "...the `ANALYTICS_PROD` warehouse..." }
],
"next_cursor": null
}

Lists the image files attached to Knowledge entries — the organization’s real logo, icon, wordmark, and favicon, plus any diagrams. Call this before showing the organization’s mark anywhere in an app. If nothing is attached, omit the company mark rather than drawing one; an app’s own icon may still be designed. Assets are exactly as visible as the entry they belong to, so the same visibility and inactive-integration rules apply.

Input

{ "scope": "org", "role": "logo-primary" }

Output

{
"items": [
{
"id": "ka_7hq",
"entry_id": "kn_ds1",
"entry_topic": "design-system",
"scope": "org",
"slug": "logo-primary",
"role": "logo-primary",
"theme": "light",
"filename": "acme-logo.svg",
"content_type": "image/svg+xml",
"byte_size": 8214,
"width": 512,
"height": 128,
"checksum_sha256": "9f2c…",
"description": "Full-color horizontal lockup. Use on light backgrounds.",
"updated_at": "2026-08-02T09:14:00Z"
}
],
"next_cursor": null
}

Paged like every other list tool: pass limit (default 50, max 100) and the previous page’s next_cursor as cursor.

When several assets share a role, prefer the app-scope one over the org-scope one, then the theme matching the surface being built, then any.

Returns one asset’s metadata and a short-lived download link. Address it by id, or by scope + topic + slug — adding integration or app_id for those scopes, since a topic is only unique within its parent. Take the address from a knowledgeAssetList result rather than guessing a topic; every failed lookup answers the same way, whether the address is unused or names an asset you cannot see.

Input

{ "scope": "org", "topic": "design-system", "slug": "logo-primary" }

Output

{
"id": "ka_7hq",
"entry_id": "kn_ds1",
"entry_topic": "design-system",
"scope": "org",
"slug": "logo-primary",
"role": "logo-primary",
"theme": "light",
"filename": "acme-logo.svg",
"content_type": "image/svg+xml",
"byte_size": 8214,
"width": 512,
"height": 128,
"checksum_sha256": "9f2c…",
"description": "Full-color horizontal lockup. Use on light backgrounds.",
"updated_at": "2026-08-02T09:14:00Z",
"download_url": "https://…",
"expires_at": "2026-08-02T09:29:00Z"
}

Fetch the bytes, check them against checksum_sha256, and commit the file into the app repository (public/logo.svg, or .greenlight/icon.svg for a branded dashboard tile). download_url expires — it is a fetch handle, never something a deployed app should reference. The bundled CLI does all three steps at once:

Terminal window
greenlight knowledge asset get org/design-system/logo-primary --out public/logo.svg

--id <uuid> addresses an asset already listed, and --integration <slug> / --app <uuid> supply the parent an integration- or app-scope address needs. Run greenlight help knowledge asset get for the full flag list.

There is no agent write path for assets: knowledgePropose is prose only, and no tool or CLI command creates, replaces, or deletes one. IT uploads them in the dashboard.

The agent write path. Always writes to the proposal queue; never mutates entries directly.

Input

{
"scope": "app",
"app_id": "app_k9x2m3p",
"topic": "architecture",
"title": "App architecture",
"body_md": "...",
"rationale": "Discovered a clean way to split the cron job from the API.",
"base_version": 3
}

Output

{
"proposal_id": "kp_xyz",
"status": "pending"
}

Returns the checks the pipeline gate enforces for your organization. Call it before generating code so you satisfy the gate up front instead of discovering a violation when the check fails.

Input

{}

No parameters. The organization comes from your authenticated session, and the listing is always the full set — there is nothing to filter.

Output

{
"policies": [
{
"id": "no-secrets-in-code",
"name": "No secrets in code",
"description": "Fails when the secret scanner finds a credential in the commit. Cannot be disabled or downgraded.",
"enforcement": "block",
"active": true
},
{
"id": "approved-base-images",
"name": "Approved base images",
"description": "Fails when a Dockerfile FROM image is absent from the org's allowlist. Silent until an allowlist is configured.",
"enforcement": "block",
"active": true,
"config": { "images": ["node:20-alpine"] }
},
{
"id": "no-env-files",
"name": "No committed .env files",
"description": "Fails when the repository contains a .env file at the evaluated commit.",
"enforcement": "block",
"active": false,
"inactive_reason": "disabled"
}
]
}

enforcement is block (fails the gate) or warn (reported, does not fail). active says whether the check runs for your organization at all, and when it does not, inactive_reason says why:

  • disabled — an administrator switched the check off.
  • awaiting_configuration — the check compares against values an administrator has not supplied yet, so it stays silent. An unconfigured base-image allowlist is the common case.

A check that is not active will not fail your pull request, so do not write code to satisfy it. Where a check takes configuration, config carries it — read approved-base-images.config.images to pick a base image that passes.

These are the shipped checks. There are no organization-authored custom rules, and the reasoning behind a policy lives in Knowledge rather than here — this tool returns data to act on, not prose to read.

Integration grants are declared in greenlight.yml, not requested through MCP. On merge, Greenlight diffs the declared grants: entries against the app’s permissions and queues IT review for any that are not auto-approved.

Each grant names the integration and the credential slug IT registered — a stable handle, not a read/write/access enum. An app declares at most one entry per integration; a second entry naming an integration already granted fails the manifest check when the pull request is opened. Where one system serves two jobs, grant the single credential whose upstream permissions cover both.

grants:
- integration: snowflake-prod
credential: analytics-read
reason: Read the sales mart for the dashboard.

Each integration carries an auth_mode and a delivery_mode; see Data brokering for what they mean.

Lists the available integrations your org has registered and the credential slugs each exposes — call it before requesting access either way: as grants: targets for an app manifest, or as requestCredentialAccess targets for your own personal access. For each integration it returns the identity model (auth_mode), how the credential reaches the app (delivery_mode), and the catalog entry it was registered from (catalog_key, null for a Custom integration). For each credential it returns a paste-ready manifest_grant_example and a request_example (the personal-access call), your own current grant status on it (caller_grant_status), and a plain-language label for what to obtain (credential_kind) with a docs_link to the upstream’s own instructions. Read-only, cursor-paginated, scoped to your org. Integrations IT has made inactive are omitted and behave as not found when a stale slug is used in a grant request. Never returns vault keys, credential values, or upstream tokens.

Input

{ "integration": "snowflake-prod", "cursor": "...", "limit": 20 }

All fields are optional; omit integration to list every registered integration.

Output

{
"items": [
{
"integration": "snowflake-prod",
"name": "Snowflake (Prod)",
"catalog_key": "snowflake",
"delivery_mode": "injected",
"env_var_name": "SNOWFLAKE_API_KEY",
"base_url": "https://acme.snowflakecomputing.com",
"auth_mode": "service_account",
"credentials": [
{
"slug": "analytics-read",
"scope": "Read access to the analytics warehouse",
"credential_kind": "Snowflake programmatic access token",
"docs_link": "https://docs.snowflake.com/en/user-guide/key-pair-auth",
"configured": true,
"approval_mode": "manual",
"manifest_grant_example": {
"integration": "snowflake-prod",
"credential": "analytics-read",
"yaml": "grants:\n - integration: snowflake-prod\n credential: analytics-read\n"
},
"request_example": {
"integration": "snowflake-prod",
"credential_slug": "analytics-read",
"call": "requestCredentialAccess({ integration: 'snowflake-prod', credential_slug: 'analytics-read', reason: '<why the user needs this>' })"
},
"caller_grant_status": "none"
}
],
"usage_note": "Injected delivery: once IT approves the grant, read the credential from process.env.SNOWFLAKE_API_KEY in the deployed app and call the upstream directly."
}
],
"next_cursor": null
}
  • catalog_key names the catalog entry the integration was registered from, or is null for a Custom integration (registered by hand). For Custom integrations, credential_kind is empty and docs_link may be null.
  • env_var_name is populated only when delivery_mode is "injected" — it’s the name the credential is delivered under at deploy. Proxied integrations leave it null; the app reaches them through the proxy base URL instead.
  • base_url is the registered upstream host the proxy calls for this integration — use it to recognize when an existing integration already covers an API you were about to ask for. It’s null for a connected database, which has no HTTP base URL.
  • A credential marked configured: false is listed but not yet servable — a grant naming it cannot be approved until IT finishes registration.
  • approval_mode is "auto" (the grant activates on merge) or "manual" (IT reviews it in the queue before the runtime value is delivered).
  • caller_grant_status is your own personal grant on this credential — none, pending, granted, denied, or revoked — so an agent never re-requests blindly.
  • Every refusal that names an integration your org hasn’t registered lists the valid available slugs back to you under details.available_integrations — the same set this tool returns, so a mistyped or abbreviated name (e.g. fmp for financial-modeling-prep) is correctable from the error alone without disclosing inactive integrations. That covers a Knowledge lookup, a requestCredentialAccess call, the pipeline’s grant check at merge, and the deploy that applies the manifest.
  • An empty items list means your org hasn’t connected any systems to Greenlight yet. That is an answer, not an error: your IT administrator registers integrations under Integrations in the dashboard, and an app that stores its own data needs no integration at all.

Requests personal access to an integration credential for the signed-in user — for local work with no app, repo, or manifest. This is the imperative counterpart of an app’s declarative grants: block: an app’s access is declared in its manifest and activates at merge; a person’s access is requested with this call and reaches the same governed proxy under their own identity. The result is granted immediately when IT has set the credential to auto-approve, otherwise pending in IT’s review queue. Re-requesting after a denial or revocation re-opens the request as pending — it never overrides IT’s decision. Every request is recorded, including ones IT later denies.

Input

{ "integration": "snowflake-prod", "credential_slug": "analytics-read", "reason": "Explore the sales mart for a local dashboard" }

Output

{
"grant_id": "5b8f0c2e-…",
"integration": "snowflake-prod",
"credential_slug": "analytics-read",
"status": "granted",
"requested_at": "2026-07-03T09:12:44.180Z",
"policy_note": "auto-granted per credential approval mode"
}

Naming an integration your org hasn’t registered returns integration.not_found with the org’s available slugs in details.available_integrations, so a mistyped slug is correctable without a second discovery call. When that list is empty, nothing is connected yet and the fix is IT registering the integration, not retrying.

Once granted, greenlight run (with no --app flag) delivers the access — see Local development. There is no dashboard form for this: requests come from the agent or CLI; the dashboard is where IT reviews them.

Asks IT to connect a system your org hasn’t connected yet. Use it when the user needs data from something that doesn’t appear in listGrantableIntegrations.

This is a different ask from requestCredentialAccess. That one requests access to a system your org already connected; this one requests the connection itself, which is a bigger decision — so it is always reviewed by a person and never auto-approved. Filing one is not a blocker: the request lands in IT’s queue and you carry on building the parts of the app that don’t need that system.

It covers systems in the Greenlight integration catalog. Naming something the catalog doesn’t carry returns connection_request.not_found with the catalogued keys, and naming something your org has already connected returns connection_request.already_connected with the available set — which is the more useful answer, because it now includes the thing you wanted.

Re-requesting is safe. An open request comes back unchanged and IT isn’t notified twice, and so does one IT has approved but hasn’t finished registering yet — asking again never wipes a decision that was already made. A previously declined request is re-raised as pending with your new reason, IT is notified again, and the reply carries IT’s decline reason in decision_note so you can tell the user what would change the answer. status is therefore only ever pending or approved here.

Input

{ "catalog_key": "notion", "reason": "The onboarding checklist needs the team handbook pages" }

Output

{
"connection_request_id": "9d1c4e2a-…",
"catalog_key": "notion",
"display_name": "Notion",
"status": "pending",
"reason": "The onboarding checklist needs the team handbook pages",
"requested_at": "2026-07-03T09:12:44.180Z",
"decided_at": null,
"decision_note": null
}

Write the reason in plain language — what the app needs the system for. Never put a credential, key, or password in it: the text is shown to IT, emailed, and recorded, and a request that looks like it contains one is refused rather than stored.

There is no dashboard form for this: requests come from the agent or CLI; the dashboard is where IT reviews them.

Lists integration grants for the app — declared grants from the manifest at the last-merge SHA, joined with approval status. Read-only. For each granted credential it also returns delivery_mode, which tells the agent how the integration runs locally: a granted integration is live (proxied through a minted token, or injected raw in-process); only user-delegated sources are fixture-only. An injected grant also carries its target env_var_name — including while status is still "pending" (a manual-approval grant), so the agent can see the name it will get without being told a value is already live before IT approves and the app redeploys.

Input

{ "app_id": "app_k9x2m3p" }

Output

{
"items": [
{
"integration": "snowflake-prod",
"credential": "analytics-read",
"declared_in_manifest": true,
"status": "approved",
"delivery_mode": "injected",
"env_var_name": "SNOWFLAKE_API_KEY"
}
]
}

Manage env-var values on the app’s shared environment. Values live in the customer’s cloud-managed secret store at apps/<app-id>/env/shared/<name>; Postgres stores metadata only. The names that reach the pod are governed by greenlight.yml (the env: block, plus managed names derived from resources: and grants:).

  • set / remove — store the vault value and the metadata row, then return { plan, executed_ops, status, note }. They do not restart the running app; it picks up the change on its next deploy (the note field restates this). sensitive: true makes the value write-once — it is never returned after creation. Reserved system names (e.g. DATABASE_URL) are rejected. set requires a reason for the audit log.
  • list — returns the full env contract: user-declared names (with vault presence) and derived managed names. Plain values are returned only when reveal: true; sensitive values are never returned.
await envSet({
app_id: 'app_k9x2m3p',
name: 'APPROVAL_SECRET',
value: '',
env: 'shared',
sensitive: true,
reason: 'Signing secret for the approvals webhook.',
});

See App environment variables for reserved names, the plain-vs-sensitive model, and the MISSING_ENV_VALUE deploy gate.

The agent develops the app on its own machine without ever holding an upstream credential. Two surfaces serve this, and neither puts a secret in an MCP response: the bundled greenlight CLI runs the app with real values delivered straight into the process, and the inspect* tools read deployed data inside the control plane and return only results. Whether a given integration is live locally or fixture-only is IT’s choice, set per credential. The full loop is in The agent protocol.

Signs the bundled greenlight CLI in as the caller, over the already-authenticated MCP session — no separate browser login. The CLI ships inside the plugin, so there’s nothing to download. The developer’s agent runs greenlight login, which prints a short code and returns right away; the agent passes it here; the server mints the CLI its own Greenlight credential — bound to the caller under their normal RBAC, the same credential the CLI uses for every command — and hands it to the CLI out of band, never in this response. Any active member of your organization can approve a code, including someone who has not built an app yet. If the agent has no working MCP connection, the same code can be approved by a person at /cli/approve in any browser instead, which asks them to confirm the code came from their own agent — nobody at your organization will ever send someone a code to type there. The code is single-use, so whichever route gets there first wins. Returns no secret to the agent.

Input

{ "code": "ABCD-1234" }

Output

{
"session_id": "cli_sess_7h2",
"app_scope": ["app_k9x2m3p"],
"expires_at": "2026-03-12T18:08:11Z"
}

Once paired, the agent runs code with greenlight run, in one of two explicit modes. App modegreenlight run --app <app_id> -- <your dev command> — resolves the app’s environment contract and grants (the same ones the deployed pod runs on) and injects the values into the running process; there is no credentials file on disk. User modegreenlight run -- <cmd> with no --app — runs on the signed-in user’s own personal grants instead (see requestCredentialAccess), for local work with no app at all. Either way, proxied integrations are reached through the same public proxy URL the deployed app uses, authenticated with a short-lived token the CLI mints from its session; the upstream secret stays in Greenlight exactly as it does in production. Granted injected credentials are delivered as their raw value in-process — every delivery is audited under the developer’s name. The app’s own Postgres runs as a local fixture database — use inspectAppDb to read deployed rows.

Runs one read-only SQL statement against the app’s own provisioned Postgres, executed inside the control plane against the vaulted credential. Returns columns and a capped set of rows — never the credential. Always available for the app’s own database, so the agent can learn the real schema and data before writing code. Audited with the calling user.

Input

{
"app_id": "app_k9x2m3p",
"statement": "select status, count(*) from expenses group by 1",
"params": []
}

Output

{
"columns": [
{ "name": "status", "type": "text" },
{ "name": "count", "type": "int8" }
],
"rows": [["approved", 142], ["pending", 17]],
"row_count": 2,
"truncated": false
}

Performs one HTTP request against a granted proxied integration, through the same grant check, credential swap, and audit path as the data proxy, with the developer’s session as the principal. Returns the response — never the upstream credential. Always available for a granted proxied integration. Injected integrations are not reachable here (they have no proxy backend); investigate those through Knowledge, a greenlight run against the granted raw value, or fixtures.

Input

{
"app_id": "app_k9x2m3p",
"integration": "snowflake-prod",
"method": "POST",
"path": "/api/v2/statements",
"body": { "statement": "select current_warehouse()" }
}

Output

{
"status": 200,
"headers": { "content-type": "application/json" },
"body": { "data": [["ANALYTICS_PROD"]] },
"truncated": false
}

Inspects the app’s own provisioned blob container, executed inside the control plane — never returns a SAS or credential. Three actions: list (prefix filter, capped count), read (one blob’s bytes, size-capped; text/JSON inline, otherwise base64), and head (metadata only). Always available for the app’s own storage. Audited with the calling user.

Input

{ "app_id": "app_k9x2m3p", "action": "list", "prefix": "exports/" }

Output

{
"items": [
{ "key": "exports/2026-03.csv", "size": 20480, "content_type": "text/csv", "last_modified": "2026-03-12T14:08:11Z" }
],
"truncated": false
}

Open and merge pull requests on the app’s repo. Direct pushes to main are blocked by branch protection; every change goes through a PR and the Greenlight Review and Policy Check (scanners + policy). The agent confirms that check passed via getPipelineRun, then calls mergePullRequest with the app id, PR number, and the exact head commit SHA it observed pass (expected_head_sha); merging applies the manifest and triggers the deploy. The call fails closed with a structured error — never falling through to the SCM provider’s merge API — if the PR has moved to a new head since, or if that head has not passed review.

The single pipeline read tool. It returns one run with its per-check verdicts and is the agent’s wait primitive after pushing a branch or opening a PR — it can long-poll server-side up to max_wait_seconds. Pass detail: "full" to enrich every check with structured remediation detail (failing file, line, rule, and a suggested fix) and, where available, a tail-capped log snapshot — so there’s no separate failure tool; the detail lives on each check. A failed deploy check also carries a blocker: whether the failure is a platform issue the agent should not try to fix in app code (owner: "operator", e.g. the cluster is out of schedulable capacity) or an app-side issue it can act on (owner: "agent"), plus a one-line explanation. Wait by commit_sha when the exact head must be confirmed — waiting by pull_request_number resolves to the newest recorded run, which can lag the current head after a force-push. Every run also carries stage_timings: where the wall-clock between merge and live actually went, split into the runner queue, the image build, and the registry push. An agent waiting on a slow run can see which stage it is waiting on rather than guessing whether the pipeline is slow or stuck. A segment reads null when it cannot be measured — the stage did not run, or the repo’s workflow file predates the version that records them.

getLogs / getMetrics / getMetricsSeries / getAppDiagnostics / curlApp / getAppPreviewUrl

Section titled “getLogs / getMetrics / getMetricsSeries / getAppDiagnostics / curlApp / getAppPreviewUrl”

Runtime observability and smoke checks against the deployed shared environment. getLogs returns a bounded, paginated window of the pod’s merged stdout/stderr; getMetrics returns the most recent per-app CPU/memory/restart snapshot; getMetricsSeries returns a downsampled CPU or memory history over a window — bucketed averages plus a min/max/avg/p95 summary — so the agent can judge a trend without reading every sample; getAppDiagnostics returns one structured health snapshot (pod phase, restart reason, probe state, resource pressure, the env contract with values masked, dependency reachability, and freshness) — the consolidated “why is my deployed app misbehaving?” read; curlApp makes an authenticated server-side request into the deployed app with the same user and app-access decision as the SSO boundary, so the agent can confirm the app actually responds for a real authorized user after deploy. If the Service cannot complete a response, it returns app.unreachable; inspect details.hit_app, then call getAppDiagnostics and getLogs.

getAppPreviewUrl covers what curlApp can’t: seeing the app. It mints a one-time URL the agent opens in its own browser tooling (IDE preview pane, headless browser) to render pages, exercise client-side code, and capture screenshots. The embedded token is single-use, expires in five minutes, and is exchanged on first navigation for a short-lived session confined to that one app’s hostname — the session carries the agent user’s real identity, so access control and the audit trail behave exactly as if that person had signed in. SSO enforcement is never bypassed or weakened.

The two are complementary, and they even take different routes: curlApp goes server-side straight to the app inside the cluster, while a preview URL travels the full public path — DNS, TLS, and the SSO-enforcing ingress. When one succeeds and the other fails, that difference tells the agent whether the app or the edge is the problem. Agents default to curlApp for response checks (no credential ever leaves the platform) and mint a preview URL when verification needs a real browser. The CLI twin is greenlight curl --app <id> --path <path>; request headers and bodies are read from --headers-file, stdin, or --body-file, never placed directly in argv.

Files a concise Greenlight platform-experience report — a platform bug, recurring friction, or a concrete improvement idea — for the Greenlight platform team. It takes a category (bug / friction / suggestion / other), a one-line title, and a Markdown body_md (optionally an app_id and a small allowlisted context with the agent runtime, OS, and plugin version). The report is recorded with its source and calling surface derived server-side, attributed to the signed-in user, and returns a feedback id. Body content is treated as untrusted (capped, rendered inert on the dashboard) and submission is rate-limited per user, so reports should summarize the platform interaction — never secrets, env values, tokens, or verbatim sensitive content. The CLI twin is greenlight feedback (body via stdin or --body-file). Org admins review submissions on the dashboard Feedback tab, alongside reports filed by dashboard users through the shell “Send feedback” dialog — and can hand a report’s title and body to Shift’s platform team, so write the report as though a Greenlight developer will read it verbatim. Who submitted it is never shared.

Returns the full markdown of the universal Greenlight Builder Skill — the core greenlight Skill by default, or a focused Skill (connected-databases, storage, platform-feedback) by skill name — byte-identical to the Skill file bundled in the plugin. Use it to re-fetch the Skill when the plugin skill isn’t loaded, after a context compaction, or when only MCP/CLI is reachable. An unrecognized skill name returns a skill.not_found error. This is a narrow, authenticated read of the same universal bytes every installation ships — not a per-org generation endpoint. The CLI twin is greenlight skill / greenlight skill show [--name <skill>].