@@ -11,6 +11,9 @@ inputs:
1111 target :
1212 description : " Path to a target config JSON file (alternative to command)"
1313 required : false
14+ targets :
15+ description : " Path to MCP config file for multi-server matrix scan (scans all servers, generates matrix comment)"
16+ required : false
1417 baseline :
1518 description : " Path to a baseline cassette file for regression verification"
1619 required : false
@@ -27,11 +30,15 @@ inputs:
2730 required : false
2831 default : " true"
2932 comment-on-pr :
30- description : " Post a markdown report as a PR comment"
33+ description : " Post a report as a PR comment"
34+ required : false
35+ default : " true"
36+ set-status :
37+ description : " Set a commit status check (green/red) on the HEAD SHA"
3138 required : false
3239 default : " true"
3340 github-token :
34- description : " GitHub token for PR comments"
41+ description : " GitHub token for PR comments and commit statuses "
3542 required : false
3643 default : ${{ github.token }}
3744 node-version :
@@ -65,57 +72,79 @@ runs:
6572 env :
6673 INPUT_COMMAND : ${{ inputs.command }}
6774 INPUT_TARGET : ${{ inputs.target }}
75+ INPUT_TARGETS : ${{ inputs.targets }}
6876 INPUT_DEEP : ${{ inputs.deep }}
6977 INPUT_SECURITY : ${{ inputs.security }}
7078 run : |
71- # Build command as a bash array to prevent injection
72- CMD_ARRAY=()
73-
74- if [ -n "$INPUT_TARGET" ]; then
75- CMD_ARRAY=(mcp-observatory run --target "$INPUT_TARGET")
76- if [ "$INPUT_DEEP" = "true" ]; then
77- CMD_ARRAY+=(--invoke)
78- fi
79- elif [ -n "$INPUT_COMMAND" ]; then
80- CMD_ARRAY=(mcp-observatory test "$INPUT_COMMAND")
81- else
82- echo "::error::Either 'command' or 'target' input is required"
83- exit 1
84- fi
85-
86- if [ "$INPUT_SECURITY" = "true" ]; then
87- CMD_ARRAY+=(--security)
88- fi
89-
90- CMD_ARRAY+=(--no-color)
91-
92- # Run and capture output
9379 ARTIFACT_DIR="${RUNNER_TEMP}/observatory"
9480 mkdir -p "$ARTIFACT_DIR"
9581
96- set +e
97- "${CMD_ARRAY[@]}" 2>&1 | tee "${ARTIFACT_DIR}/output.txt"
98- EXIT_CODE=$?
99- set -e
100-
101- # Find the artifact file
102- ARTIFACT_PATH=$(find .mcp-observatory/runs -name "*.json" -type f 2>/dev/null | sort | tail -1)
82+ # Multi-server matrix scan
83+ if [ -n "$INPUT_TARGETS" ]; then
84+ SCAN_ARRAY=(mcp-observatory scan --config "$INPUT_TARGETS")
85+ if [ "$INPUT_SECURITY" = "true" ]; then
86+ SCAN_ARRAY+=(--security)
87+ fi
88+ SCAN_ARRAY+=(--no-color)
89+
90+ set +e
91+ "${SCAN_ARRAY[@]}" 2>&1 | tee "${ARTIFACT_DIR}/output.txt"
92+ set -e
93+
94+ # Collect all artifacts and determine gate
95+ GATE="pass"
96+ for f in .mcp-observatory/runs/*.json; do
97+ [ -f "$f" ] || continue
98+ FILE_GATE=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$f','utf8')).gate)" 2>/dev/null || echo "unknown")
99+ if [ "$FILE_GATE" = "fail" ]; then
100+ GATE="fail"
101+ fi
102+ done
103+ echo "gate=${GATE}" >> "$GITHUB_OUTPUT"
104+ echo "artifact_path=${ARTIFACT_DIR}" >> "$GITHUB_OUTPUT"
103105
104- if [ -n "$ARTIFACT_PATH" ]; then
105- cp "$ARTIFACT_PATH" "${ARTIFACT_DIR}/run.json"
106- echo "artifact_path=${ARTIFACT_DIR}/run.json" >> "$GITHUB_OUTPUT"
106+ # Generate CI report for PR comment
107+ mcp-observatory ci-report --format markdown --no-color > "${ARTIFACT_DIR}/report.md" 2>/dev/null || true
107108
108- # Extract gate from artifact using node (avoid python dependency)
109- GATE=$(node -e "const fs=require('fs'); const d=JSON.parse(fs.readFileSync('${ARTIFACT_DIR}/run.json','utf8')); console.log(d.gate)" 2>/dev/null || echo "unknown")
110- echo "gate=${GATE}" >> "$GITHUB_OUTPUT"
109+ # Single-server scan
111110 else
112- echo "gate=fail" >> "$GITHUB_OUTPUT"
113- echo "artifact_path=" >> "$GITHUB_OUTPUT"
114- fi
111+ CMD_ARRAY=()
112+ if [ -n "$INPUT_TARGET" ]; then
113+ CMD_ARRAY=(mcp-observatory run --target "$INPUT_TARGET")
114+ if [ "$INPUT_DEEP" = "true" ]; then
115+ CMD_ARRAY+=(--invoke)
116+ fi
117+ elif [ -n "$INPUT_COMMAND" ]; then
118+ CMD_ARRAY=(mcp-observatory test "$INPUT_COMMAND")
119+ else
120+ echo "::error::Either 'command', 'target', or 'targets' input is required"
121+ exit 1
122+ fi
115123
116- # Generate PR comment report
117- if [ -n "$ARTIFACT_PATH" ]; then
118- mcp-observatory report "$ARTIFACT_PATH" --format pr-comment --no-color > "${ARTIFACT_DIR}/report.md" 2>/dev/null || true
124+ if [ "$INPUT_SECURITY" = "true" ]; then
125+ CMD_ARRAY+=(--security)
126+ fi
127+ CMD_ARRAY+=(--no-color)
128+
129+ set +e
130+ "${CMD_ARRAY[@]}" 2>&1 | tee "${ARTIFACT_DIR}/output.txt"
131+ set -e
132+
133+ ARTIFACT_PATH=$(find .mcp-observatory/runs -name "*.json" -type f 2>/dev/null | sort | tail -1)
134+ if [ -n "$ARTIFACT_PATH" ]; then
135+ cp "$ARTIFACT_PATH" "${ARTIFACT_DIR}/run.json"
136+ echo "artifact_path=${ARTIFACT_DIR}/run.json" >> "$GITHUB_OUTPUT"
137+ GATE=$(node -e "const fs=require('fs'); const d=JSON.parse(fs.readFileSync('${ARTIFACT_DIR}/run.json','utf8')); console.log(d.gate)" 2>/dev/null || echo "unknown")
138+ echo "gate=${GATE}" >> "$GITHUB_OUTPUT"
139+ else
140+ echo "gate=fail" >> "$GITHUB_OUTPUT"
141+ echo "artifact_path=" >> "$GITHUB_OUTPUT"
142+ fi
143+
144+ # Generate PR comment report
145+ if [ -n "$ARTIFACT_PATH" ]; then
146+ mcp-observatory report "$ARTIFACT_PATH" --format pr-comment --no-color > "${ARTIFACT_DIR}/report.md" 2>/dev/null || true
147+ fi
119148 fi
120149
121150 - name : Verify against baseline
@@ -126,15 +155,12 @@ runs:
126155 INPUT_COMMAND : ${{ inputs.command }}
127156 INPUT_TARGET : ${{ inputs.target }}
128157 run : |
129- # Build verify command as a bash array
130158 VERIFY_ARRAY=(mcp-observatory verify "$INPUT_BASELINE")
131-
132159 if [ -n "$INPUT_TARGET" ]; then
133160 VERIFY_ARRAY+=(--target "$INPUT_TARGET")
134161 elif [ -n "$INPUT_COMMAND" ]; then
135162 VERIFY_ARRAY+=("$INPUT_COMMAND")
136163 fi
137-
138164 VERIFY_ARRAY+=(--no-color)
139165
140166 set +e
@@ -146,6 +172,26 @@ runs:
146172 echo "::warning::Baseline verification detected changes"
147173 fi
148174
175+ - name : Set commit status
176+ if : inputs.set-status == 'true' && github.event_name == 'pull_request'
177+ shell : bash
178+ env :
179+ GH_TOKEN : ${{ inputs.github-token }}
180+ GATE : ${{ steps.run.outputs.gate }}
181+ run : |
182+ STATE="success"
183+ DESC="All clear"
184+ if [ "$GATE" = "fail" ]; then
185+ STATE="failure"
186+ DESC="Issues detected"
187+ fi
188+ gh api "repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }}" \
189+ -f state="$STATE" \
190+ -f description="$DESC" \
191+ -f context="MCP Observatory" \
192+ -f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
193+ 2>/dev/null || echo "::warning::Could not set commit status"
194+
149195 - name : Comment on PR
150196 if : inputs.comment-on-pr == 'true' && github.event_name == 'pull_request'
151197 shell : bash
@@ -161,7 +207,6 @@ runs:
161207 exit 0
162208 fi
163209
164- # pr-comment format already includes header and footer
165210 COMMENT_FILE="${RUNNER_TEMP}/observatory/comment.md"
166211 cp "$REPORT_FILE" "$COMMENT_FILE"
167212
0 commit comments