Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/build_mcp_server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ on:
paths:
- ".github/image/Dockerfile"
- ".github/workflows/build_mcp_server.yml"
- "catalog/**"
- "mcp-server/**"
- "skills/**"

jobs:
build:
Expand Down Expand Up @@ -42,6 +44,14 @@ jobs:
with:
toolchain: stable

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
Comment on lines +47 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify workflows still have tag-only action refs
rg -n '^\s*uses:\s*[^@]+@v?[0-9]+(\.[0-9]+)*\s*$' .github/workflows

Repository: txpipe/metis

Length of output: 2413


Pin actions/setup-node to a commit SHA (avoid @v4 tags).

.github/workflows/build_mcp_server.yml uses actions/setup-node@v4 at line 48, which weakens supply-chain guarantees under strict CI policies.

🔧 Suggested hardening
-      - name: Set up Node.js
-        uses: actions/setup-node@v4
+      - name: Set up Node.js
+        uses: actions/setup-node@<full_commit_sha> # v4
         with:
           node-version: '22'
🧰 Tools
🪛 zizmor (1.25.2)

[error] 48-48: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build_mcp_server.yml around lines 47 - 50, The workflow
step using "uses: actions/setup-node@v4" (the "Set up Node.js" step) should be
pinned to a specific commit SHA instead of the "`@v4`" tag; update the "uses"
field to the full commit SHA for actions/setup-node (obtain the latest stable
commit SHA from the actions/setup-node repository) so the CI uses an immutable
reference and not a floating tag.


- name: Generate skill catalog
run: node catalog/scripts/generate-skill-catalog.mjs
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Run cargo build
run: cargo build --target ${{ matrix.target }} ${{ matrix.args }}
working-directory: mcp-server
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/check_catalog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ on:
- main
paths:
- ".github/workflows/check_catalog.yml"
- "catalog/**"
- "frontends/catalog/**"
- "skills/**"
pull_request:
branches:
- main
paths:
- ".github/workflows/check_catalog.yml"
- "catalog/**"
- "frontends/catalog/**"
- "skills/**"

jobs:
check:
Expand All @@ -39,6 +43,10 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Verify generated skill catalog
working-directory: .
run: node catalog/scripts/generate-skill-catalog.mjs

- name: Run typecheck
run: pnpm run typecheck

Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/check_mcp_server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ on:
- main
paths:
- ".github/workflows/check_mcp_server.yml"
- "catalog/**"
- "mcp-server/**"
- "skills/**"
pull_request:
branches:
- main
paths:
- ".github/workflows/check_mcp_server.yml"
- "catalog/**"
- "mcp-server/**"
- "skills/**"

jobs:
lint:
Expand All @@ -24,5 +28,14 @@ jobs:
- name: Checkout
uses: actions/checkout@v3

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
Comment on lines +31 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and display the relevant workflow section
ls -la .github/workflows/check_mcp_server.yml
nl -ba .github/workflows/check_mcp_server.yml | sed -n '1,120p'

# Search for other uses of actions/setup-node in the repo (pinning patterns)
rg -n "uses:\s*actions/setup-node@" -S .github/workflows || true
rg -n "zizmor|unpinned|pin(ed)? action" -S .github/workflows .github || true

Repository: txpipe/metis

Length of output: 204


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=".github/workflows/check_mcp_server.yml"
echo "== Workflow file =="

# Show first ~120 lines with line numbers (no nl dependency)
awk '{printf "%5d  %s\n", NR, $0}' "$file" | sed -n '1,140p'

echo "== Search for actions/setup-node usage =="

rg -n "uses:\s*actions/setup-node@" .github/workflows || true
rg -n "uses:\s*actions/" .github/workflows | head -n 200 || true

echo "== Search for unpinned/mutable action references patterns =="

rg -n "uses:\s*[^@]+@[vV]([0-9]+|\w+)" .github/workflows .github || true
rg -n "zizmor|unpinned|pin(ed)? action" .github/workflows .github || true

Repository: txpipe/metis

Length of output: 8394


Pin actions/setup-node to a full commit SHA (not @v4).

.github/workflows/check_mcp_server.yml uses actions/setup-node@v4 (mutable tag), weakening CI supply-chain guarantees.

Suggested fix
-      - name: Set up Node.js
-        uses: actions/setup-node@v4
+      - name: Set up Node.js
+        uses: actions/setup-node@<full_commit_sha>
         with:
           node-version: '22'
🧰 Tools
🪛 zizmor (1.25.2)

[error] 32-32: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/check_mcp_server.yml around lines 31 - 34, The workflow
step "Set up Node.js" uses the mutable tag actions/setup-node@v4; replace that
with the repository pinned to a full commit SHA for actions/setup-node (i.e.,
change the uses value from "actions/setup-node@v4" to
"actions/setup-node@<full-commit-sha>") so the CI action is pinned immutably and
supply-chain risk is reduced.


- name: Generate skill catalog
working-directory: .
run: node catalog/scripts/generate-skill-catalog.mjs

- name: Clippy check lints
run: cargo clippy -- -D warnings
13 changes: 13 additions & 0 deletions .github/workflows/test_mcp_server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ on:
- main
paths:
- ".github/workflows/test_mcp_server.yml"
- "catalog/**"
- "mcp-server/**"
- "skills/**"
pull_request:
branches:
- main
paths:
- ".github/workflows/test_mcp_server.yml"
- "catalog/**"
- "mcp-server/**"
- "skills/**"

jobs:
lint:
Expand All @@ -24,5 +28,14 @@ jobs:
- name: Checkout
uses: actions/checkout@v3

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
Comment on lines +31 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP 'uses:\s*[^@\s]+/[^@\s]+@(?![0-9a-f]{40}\b)' .github/workflows

Repository: txpipe/metis

Length of output: 2792


Pin actions/setup-node to a full commit SHA.

.github/workflows/test_mcp_server.yml line 32 uses a floating tag (actions/setup-node@v4), which weakens supply-chain guarantees versus SHA-pinning.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 32-32: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/test_mcp_server.yml around lines 31 - 34, The workflow
step named "Set up Node.js" currently references actions/setup-node@v4 which is
a floating tag; replace that reference with the action's full commit SHA (pin to
a specific immutable revision) so the step uses
actions/setup-node@<full-commit-sha> instead of `@v4`; keep the existing with:
node-version: '22' configuration and update any related documentation or
comments to note the pinned SHA.


- name: Generate skill catalog
working-directory: .
run: node catalog/scripts/generate-skill-catalog.mjs

- name: Test
run: cargo test
84 changes: 71 additions & 13 deletions catalog/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
# Extension Catalog
# MCP Catalogs

This directory contains the Supernode MCP extension catalog.
This directory contains the catalog documents consumed by the Supernode MCP server.

`extension-catalog.json` is the catalog document consumed by the MCP server. It is also the payload that should be published as the official OCI catalog artifact.
- `extension-catalog.json`: installable extension contracts and chart references.
- `skill-catalog.manifest.json`: editable metadata for operational skill guides.
- `skill-catalog.json`: generated, tracked skill catalog payload with embedded markdown content.

## Contract
Run this after editing `skill-catalog.manifest.json` or `../skills/*.md`:

The catalog document uses this top-level shape:
```sh
node catalog/scripts/generate-skill-catalog.mjs
```

`skill-catalog.json` is committed so MCP builds and catalog consumers have a ready-to-use bundled skill catalog. Regenerate it locally after editing skill metadata or markdown, then commit the updated artifact with the source changes.

## Extension Catalog Contract

`extension-catalog.json` uses this top-level shape:

```json
{
Expand All @@ -28,52 +38,94 @@ Each extension entry describes the MCP-facing extension contract:
- `outputs`: user-facing endpoints provided by workloads of this extension.
- `chart`: OCI Helm chart reference used by MCP install and upgrade operations.

## Skill Catalog Contract

`skill-catalog.json` uses this top-level shape:

```json
{
"schemaVersion": "supernode.skillCatalog/v1",
"skills": []
}
```

Each skill entry describes one operational guide:

- `id`: canonical URI-safe skill ID used by `supernode://skills/{skillId}`.
- `title` and `description`: human-readable metadata for agents and users.
- `tags`: discovery labels.
- `extensions`: related extension IDs, when applicable.
- `tools`: MCP tools used by the guide.
- `content`: markdown guide content embedded from `../skills/*.md`.

Edit `skill-catalog.manifest.json` rather than `skill-catalog.json` directly. The manifest stores the same metadata plus `contentPath`; the generator embeds the referenced markdown into the publishable JSON document. Because `skill-catalog.json` is generated and tracked, changes to skill metadata or markdown should include the regenerated catalog artifact.

## Trusted Sources

By default, MCP only trusts official Supernode OCI references:

- Catalog artifacts must be fetched from `oci.supernode.store`.
- Extension charts must be under `oci://oci.supernode.store/extensions/{extensionId}`.

This is intentional. The catalog influences what agents recommend and what MCP can install or execute for metrics collection. Treat it as a supply-chain input, not a cosmetic document.
This is intentional. Extension catalogs influence what MCP can install and execute for metrics collection. Skill catalogs are prompt-bearing operational guidance and can influence agent behavior. Treat both as supply-chain inputs, not cosmetic documents.

For local development only, MCP can be started with:

```text
MCP_EXTENSION_CATALOG_ALLOW_UNTRUSTED=true
MCP_SKILL_CATALOG_ALLOW_UNTRUSTED=true
```

Do not enable this in production. An untrusted catalog can point MCP at untrusted charts, mislead agents through descriptions and schemas, or define unsafe metrics collection metadata.
Do not enable these in production.

## Publishing

The official catalog should be published as a standalone OCI artifact containing `extension-catalog.json` as a JSON layer.
The official catalogs should be published as standalone OCI artifacts containing a single JSON layer. Use the publish script rather than invoking `oras` directly; it regenerates `skill-catalog.json` immediately before pushing so the OCI artifact matches the current manifest and markdown.

Recommended layer media type:
Recommended extension catalog layer media type:

```text
application/vnd.supernode.extension-catalog.v1+json
```

Example using `oras`:
Recommended skill catalog layer media type:

```text
application/vnd.supernode.skill-catalog.v1+json
```

Publish both catalogs with:

```sh
CATALOG_TAG=0.1.0 catalog/scripts/publish-catalogs.sh
```

The script runs the equivalent of:

```sh
node catalog/scripts/generate-skill-catalog.mjs

oras push \
oci.supernode.store/extension-catalog:0.1.0 \
extension-catalog.json:application/vnd.supernode.extension-catalog.v1+json
oci.supernode.store/extension-catalog:${CATALOG_TAG} \
catalog/extension-catalog.json:application/vnd.supernode.extension-catalog.v1+json

oras push \
oci.supernode.store/skill-catalog:${CATALOG_TAG} \
catalog/skill-catalog.json:application/vnd.supernode.skill-catalog.v1+json
```

Production deployments should prefer digest-pinned catalog references when practical:

```text
oci://oci.supernode.store/extension-catalog@sha256:<digest>
oci://oci.supernode.store/skill-catalog@sha256:<digest>
```

Tag references are supported and convenient for development or release channels, but digest references are safer because they are immutable.

## Validation

MCP validates the catalog at load time:
MCP validates extension catalogs at load time:

- `schemaVersion` must be `supernode.extensionCatalog/v1`.
- extension IDs must be unique and non-empty.
Expand All @@ -83,4 +135,10 @@ MCP validates the catalog at load time:
- chart references must be OCI references.
- by default, chart references must point to `oci://oci.supernode.store/extensions/{extensionId}`.

MCP validates skill catalogs at load time:

- `schemaVersion` must be `supernode.skillCatalog/v1`.
- skill IDs must be unique, non-empty, and URI-safe.
- `title`, `description`, and `content` must be non-empty.

Longer term, official catalog artifacts should also be signed and verified before MCP accepts them.
40 changes: 40 additions & 0 deletions catalog/scripts/generate-skill-catalog.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { readFileSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const scriptDir = dirname(fileURLToPath(import.meta.url));
const catalogDir = resolve(scriptDir, "..");
const manifestPath = resolve(catalogDir, "skill-catalog.manifest.json");
const outputPath = resolve(catalogDir, "skill-catalog.json");

const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));

if (manifest.schemaVersion !== "supernode.skillCatalogManifest/v1") {
throw new Error(
`unsupported skill catalog manifest schema: ${manifest.schemaVersion}`,
);
}

const seen = new Set();
const skills = manifest.skills.map(({ contentPath, ...skill }) => {
if (!skill.id || seen.has(skill.id)) {
throw new Error(`invalid or duplicate skill id: ${skill.id}`);
}
seen.add(skill.id);

if (!contentPath) {
throw new Error(`missing contentPath for skill: ${skill.id}`);
}

return {
...skill,
content: readFileSync(resolve(catalogDir, contentPath), "utf8"),
};
});

const catalog = {
schemaVersion: "supernode.skillCatalog/v1",
skills,
};

writeFileSync(outputPath, `${JSON.stringify(catalog, null, 2)}\n`);
16 changes: 16 additions & 0 deletions catalog/scripts/publish-catalogs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
REGISTRY="${REGISTRY:-oci.supernode.store}"
CATALOG_TAG="${CATALOG_TAG:-0.1.0}"

node "${ROOT_DIR}/catalog/scripts/generate-skill-catalog.mjs"

oras push \
"${REGISTRY}/extension-catalog:${CATALOG_TAG}" \
"${ROOT_DIR}/catalog/extension-catalog.json:application/vnd.supernode.extension-catalog.v1+json"

oras push \
"${REGISTRY}/skill-catalog:${CATALOG_TAG}" \
"${ROOT_DIR}/catalog/skill-catalog.json:application/vnd.supernode.skill-catalog.v1+json"
Loading
Loading