From d39c2c8254d5339eb0f1d546ea52c6c504aa1449 Mon Sep 17 00:00:00 2001 From: Ryan D McGuire Date: Mon, 5 Oct 2026 16:45:21 -0400 Subject: [PATCH] update agents md --- AGENTS.md | 46 +++++++++++++++++++++++++++++-------- images/node-agent/README.md | 16 +++++++++++++ 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index bace386..0fc2277 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -142,15 +142,41 @@ from a sibling repo — several were a major behind. ## Downstream consumers -`node-agent` backs the shell MCP server in the **`50W/kube-manifests`** repo. When -its toolset changes, update all three of these or agents will not know what they -can run: +`node-agent` backs **three** MCP servers in the **`50W/kube-manifests`** repo: -- `toolhive/mcpserver-shell.yaml` — the `WHAT'S IN THIS CONTAINER` comment block. -- `toolhive/mcptoolconfig-shell.yaml` — `toolsOverride.run_process.description`, - the tool description models actually read. -- `ai/kagent/remotemcpserver-shell.yaml` — `spec.description`. +- **`shell`** — an arbitrary command runner. When this image's toolset changes, + update all three of these or agents will not know what they can run: + - `toolhive/mcpserver-shell.yaml` — the `WHAT'S IN THIS CONTAINER` comment block. + - `toolhive/mcptoolconfig-shell.yaml` — `toolsOverride.run_process.description`, + the tool description models actually read. + - `ai/kagent/remotemcpserver-shell.yaml` — `spec.description`. +- **`yamllint`** — a small stdio MCP server shipped as a ConfigMap + (`toolhive/cm-yamllint-mcp.yaml`) that runs on this image and shells out to + `yamllint` and `kubectl` to expose `lint_yaml` / `lint_manifest`. This is why + `yamllint` is in the apk list; it is the only reason that package is here. +- **`fetch-jq`** — another ConfigMap-shipped stdio MCP server + (`toolhive/cm-fetch-jq-mcp.yaml`), exposing `fetch_json_jq` / `fetch_json`. It + pipes a fetched HTTP response straight into `jq`'s stdin (`spawnSync` with an + `input:` option, no temp file), so a model never has to scan a large raw JSON + array by eye to find one value. `jq` was already in the apk list before this + consumer existed — nothing had to be added for it, unlike `yamllint`. -That pod runs with no Kubernetes credentials, HTTPS-only egress, a read-only root -filesystem and all capabilities dropped, which is what `node-agent`'s smoke test -mirrors. Do not add a pull secret there — the registry is public. +Neither `shell` nor `yamllint` gets a pull secret — the registry is public. Same +for `fetch-jq`. + +The three pods have very different privileges, so **do not assume `kubectl` in +this image implies cluster access** — that depends entirely on the consuming +pod's ServiceAccount: + +| | `shell` | `yamllint` | `fetch-jq` | +| --- | --- | --- | --- | +| ServiceAccount | cluster-wide **read** (incl. pod logs) | **no role bindings at all** | **none mounted** | +| Egress | HTTPS + API server | API server only, no internet | HTTPS to whatever URL a caller passes — no API server | +| `kubectl` can | `get`/`list`/`watch` most kinds | only fetch the OpenAPI schema | n/a — never invoked | + +`fetch-jq` is the odd one out: it's the only one of the three with open internet +egress and the only one with zero Kubernetes access of any kind — see +`toolhive/mcpserver-fetch-jq.yaml` for why it mounts no ServiceAccount at all. + +`node-agent`'s smoke test mirrors the stricter shared parts of that contract — +non-root, read-only root filesystem, all capabilities dropped, `/tmp` a tmpfs. diff --git a/images/node-agent/README.md b/images/node-agent/README.md index ed1a0b2..c9a0dca 100644 --- a/images/node-agent/README.md +++ b/images/node-agent/README.md @@ -58,6 +58,22 @@ Client-side validation needs no RBAC at all — it only fetches the OpenAPI sche which every authenticated identity may do — but it does need to reach the API server. With no API access it fails closed on valid manifests (`failed to download openapi`). +### Filtered JSON fetch + +`jq` also backs the `fetch-jq` MCP server in `50W/kube-manifests` +(`toolhive/cm-fetch-jq-mcp.yaml`), which exposes `fetch_json_jq`: fetch a URL, +then filter the response with a jq expression before any of it reaches a +model's context. The body is piped straight into `jq`'s stdin, never written to +disk: + +```sh +curl -s "$URL" | jq -c '[.[-24:][] | {time_tag, value}]' +``` + +Unlike `yamllint`'s consumer, `fetch-jq` needs real internet egress — the URLs +it fetches are external APIs, not in-cluster services — and mounts no +ServiceAccount at all, since it never touches the Kubernetes API. + ## Environment | Variable | Value | Why |