Skip to content

Commit e26bd30

Browse files
authored
Add action to generate and upload MCP-Eval badges from JSON report (#34)
## Summary - Add a badge generation action and update workflow to use it. ## Checklist - [ ] Tests added/updated - [ ] Docs updated (README, docs/*.mdx) - [x] Lint passes locally - [ ] Linked issue (if any) ## Screenshots / Logs If applicable, add screenshots or logs. ## Breaking Changes - [ ] Yes - [x] No If yes, describe the migration path.
1 parent 3303381 commit e26bd30

File tree

8 files changed

+390
-255
lines changed

8 files changed

+390
-255
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
name: "Generate MCP-Eval Badges"
2+
description: "Generate badges from MCP-Eval JSON report using shields.io"
3+
branding:
4+
icon: award
5+
color: green
6+
inputs:
7+
report-path:
8+
description: "Path to the MCP-Eval JSON report file"
9+
required: true
10+
output-dir:
11+
description: "Directory to write generated badge files (optional, for caching)"
12+
default: "mcpeval-reports/badges"
13+
required: false
14+
format:
15+
description: "Output format: svg, endpoint, or both (default: both)"
16+
default: "both"
17+
required: false
18+
tests-label:
19+
description: "Label text for the tests badge"
20+
default: "mcp-tests"
21+
required: false
22+
coverage-label:
23+
description: "Label text for the coverage badge"
24+
default: "mcp-cov"
25+
required: false
26+
upload-artifacts:
27+
description: "Upload badges as workflow artifacts"
28+
default: "false"
29+
required: false
30+
artifact-name:
31+
description: "Name for the uploaded badge artifacts"
32+
default: "mcpeval-badges"
33+
required: false
34+
outputs:
35+
tests-badge-path:
36+
description: "Path to the generated tests badge SVG (if output-dir is set)"
37+
value: ${{ steps.generate.outputs.tests_badge_path }}
38+
coverage-badge-path:
39+
description: "Path to the generated coverage badge SVG (if output-dir is set)"
40+
value: ${{ steps.generate.outputs.coverage_badge_path }}
41+
runs:
42+
using: "composite"
43+
steps:
44+
- name: Generate badges
45+
id: generate
46+
shell: bash
47+
run: |
48+
set -euo pipefail
49+
50+
# Check if local script exists, otherwise fetch from upstream
51+
if [ -f "scripts/generate_badges.py" ]; then
52+
echo "Using local badge generation script"
53+
SCRIPT_PATH="scripts/generate_badges.py"
54+
else
55+
echo "Fetching badge generation script from upstream"
56+
# Create a temporary directory for the mcp-eval script
57+
mkdir -p .mcp-eval-action
58+
cd .mcp-eval-action
59+
60+
# Initialize git and configure sparse checkout
61+
git init
62+
git remote add origin https://github.com/lastmile-ai/mcp-eval.git
63+
git config core.sparseCheckout true
64+
65+
# Configure sparse checkout to only get the script we need
66+
echo "scripts/generate_badges.py" >> .git/info/sparse-checkout
67+
68+
# Fetch and checkout the specific file (pinned to a stable commit)
69+
# TODO: Update this to a specific tag/release when available
70+
git fetch --depth=1 origin main
71+
git checkout origin/main
72+
73+
# Move back to the workspace root
74+
cd ..
75+
SCRIPT_PATH=".mcp-eval-action/scripts/generate_badges.py"
76+
fi
77+
78+
# Run the badge generation script with uv
79+
uv run "$SCRIPT_PATH" \
80+
--report "${{ inputs.report-path }}" \
81+
--outdir "${{ inputs.output-dir }}" \
82+
--label-tests "${{ inputs.tests-label }}" \
83+
--label-cov "${{ inputs.coverage-label }}" \
84+
--format "${{ inputs.format }}"
85+
86+
# Set output paths if badges were generated
87+
if [ -n "${{ inputs.output-dir }}" ]; then
88+
if [ -f "${{ inputs.output-dir }}/tests.svg" ] && [ -f "${{ inputs.output-dir }}/coverage.svg" ]; then
89+
echo "tests_badge_path=$(realpath ${{ inputs.output-dir }}/tests.svg)" >> $GITHUB_OUTPUT
90+
echo "coverage_badge_path=$(realpath ${{ inputs.output-dir }}/coverage.svg)" >> $GITHUB_OUTPUT
91+
fi
92+
fi
93+
94+
- name: Upload badge artifacts
95+
if: ${{ inputs.upload-artifacts == 'true' && inputs.output-dir != '' }}
96+
uses: actions/upload-artifact@v4
97+
with:
98+
name: ${{ inputs.artifact-name }}
99+
path: ${{ inputs.output-dir }}
100+
retention-days: 14
101+
if-no-files-found: warn

.github/workflows/mcpeval-reusable.yml

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ on:
4343
required: false
4444
type: boolean
4545
default: false
46+
deploy-pages-branch:
47+
required: false
48+
type: string
49+
default: 'refs/heads/main'
4650
secrets:
4751
ANTHROPIC_API_KEY:
4852
required: false
@@ -53,6 +57,9 @@ jobs:
5357
run:
5458
name: Run MCP-Eval
5559
runs-on: ubuntu-latest
60+
permissions:
61+
contents: read
62+
pull-requests: write
5663
outputs:
5764
json: ${{ steps.mcpeval.outputs.results-json-path }}
5865
md: ${{ steps.mcpeval.outputs.results-md-path }}
@@ -63,7 +70,7 @@ jobs:
6370

6471
- name: Run MCP-Eval
6572
id: mcpeval
66-
uses: ./.github/actions/mcp-eval/run
73+
uses: lastmile-ai/mcp-eval/.github/actions/mcp-eval/run@main
6774
with:
6875
python-version: ${{ inputs.python-version }}
6976
working-directory: ${{ inputs.working-directory }}
@@ -78,21 +85,49 @@ jobs:
7885
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
7986
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
8087

81-
- name: Generate badges
82-
run: |
83-
set -euo pipefail
84-
uv run scripts/generate_badges.py --report "${{ steps.mcpeval.outputs.results-json-path }}" --outdir mcpeval-reports/badges
85-
86-
- name: Upload badge artifacts
87-
uses: actions/upload-artifact@v4
88+
- name: Generate and upload badges
89+
uses: lastmile-ai/mcp-eval/.github/actions/mcp-eval/badges@main
8890
with:
89-
name: mcpeval-badges
90-
path: mcpeval-reports/badges
91+
report-path: ${{ steps.mcpeval.outputs.results-json-path }}
92+
output-dir: badges
93+
format: 'both'
94+
upload-artifacts: 'true'
95+
artifact-name: mcpeval-badges
96+
97+
# Post the Markdown report as a sticky PR comment for easy review
98+
- name: Comment PR with MCP-Eval report
99+
if: ${{ github.event_name == 'pull_request' }}
100+
uses: actions/github-script@v7
101+
env:
102+
REPORT_PATH: ${{ steps.mcpeval.outputs.results-md-path }}
103+
with:
104+
script: |
105+
const fs = require('fs');
106+
const path = process.env.REPORT_PATH;
107+
let body = '<!-- mcpeval-report -->\n';
108+
body += '## MCP-Eval Report\n\n';
109+
try {
110+
const content = fs.readFileSync(path, 'utf8');
111+
body += content;
112+
} catch (e) {
113+
body += '_No report found at ' + path + '_\n';
114+
}
115+
const { owner, repo } = context.repo;
116+
const issue_number = context.issue.number;
117+
118+
// Find existing sticky comment
119+
const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number, per_page: 100 });
120+
const previous = comments.find(c => c.user.type === 'Bot' && c.body.startsWith('<!-- mcpeval-report -->'));
121+
if (previous) {
122+
await github.rest.issues.updateComment({ owner, repo, comment_id: previous.id, body });
123+
} else {
124+
await github.rest.issues.createComment({ owner, repo, issue_number, body });
125+
}
91126
92127
pages:
93-
name: Publish Report to Pages
128+
name: Publish report and badges to GitHub Pages
94129
needs: run
95-
if: ${{ inputs.deploy-pages }}
130+
if: ${{ inputs.deploy-pages && github.event_name == 'push' && github.ref == inputs.deploy-pages-branch }}
96131
runs-on: ubuntu-latest
97132
permissions:
98133
pages: write
@@ -101,8 +136,20 @@ jobs:
101136
environment:
102137
name: github-pages
103138
url: ${{ steps.deployment.outputs.page_url }}
139+
concurrency:
140+
group: "pages"
141+
cancel-in-progress: false
104142
steps:
105-
- name: Download artifacts
143+
- name: Checkout
144+
uses: actions/checkout@v4
145+
146+
- name: Download badge artifacts
147+
uses: actions/download-artifact@v4
148+
with:
149+
name: mcpeval-badges
150+
path: badges/
151+
152+
- name: Download report artifacts
106153
uses: actions/download-artifact@v4
107154
with:
108155
name: ${{ inputs.artifact-name }}
@@ -112,13 +159,23 @@ jobs:
112159
run: |
113160
set -euo pipefail
114161
mkdir -p site
162+
163+
# Copy badges to site
164+
if [[ -d badges ]]; then
165+
cp -r badges site/
166+
fi
167+
168+
# Copy HTML report as index.html
115169
if [[ -f "mcpeval-artifacts/${{ inputs.reports-dir }}/${{ inputs.html-report }}" ]]; then
116170
cp mcpeval-artifacts/${{ inputs.reports-dir }}/${{ inputs.html-report }} site/index.html
117171
else
118172
file=$(find mcpeval-artifacts -name "*.html" | head -n 1 || true)
119173
if [[ -n "$file" ]]; then cp "$file" site/index.html; else echo '<h1>No report available</h1>' > site/index.html; fi
120174
fi
121175
176+
- name: Setup Pages
177+
uses: actions/configure-pages@v5
178+
122179
- name: Upload Pages artifact
123180
uses: actions/upload-pages-artifact@v3
124181
with:

.github/workflows/mcpeval.yml

Lines changed: 8 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -10,129 +10,15 @@ concurrency:
1010
group: ${{ github.workflow }}-${{ github.ref }}
1111
cancel-in-progress: true
1212

13-
permissions:
14-
contents: read
15-
pull-requests: write
16-
1713
jobs:
18-
tests:
19-
name: Run MCP-Eval
20-
runs-on: ubuntu-latest
21-
defaults:
22-
run:
23-
shell: bash
24-
steps:
25-
- name: Checkout
26-
uses: actions/checkout@v4
27-
28-
# Tip: set your LLM provider keys in repo/org secrets
29-
# Settings discovery follows mcp-agent/mcpeval configs in your repo.
30-
- name: Run MCP-Eval (uv)
31-
id: mcpeval
32-
uses: ./.github/actions/mcp-eval/run
33-
with:
34-
python-version: "3.11"
35-
working-directory: .
36-
run-args: "-v"
37-
tests: tests/
38-
reports-dir: mcpeval-reports
39-
json-report: mcpeval-results.json
40-
markdown-report: mcpeval-results.md
41-
html-report: mcpeval-results.html
42-
artifact-name: mcpeval-artifacts
43-
pr-comment: "true"
44-
set-summary: "true"
45-
upload-artifacts: "true"
46-
env:
47-
# Provide at least one provider key; both are supported
48-
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
49-
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
50-
51-
- name: Generate badges
52-
run: |
53-
set -euo pipefail
54-
uv run scripts/generate_badges.py --report "${{ steps.mcpeval.outputs.results-json-path }}" --outdir mcpeval-reports/badges
55-
56-
- name: Upload badge artifacts
57-
uses: actions/upload-artifact@v4
58-
with:
59-
name: mcpeval-badges
60-
path: mcpeval-reports/badges
61-
62-
# Post the Markdown report as a sticky PR comment for easy review
63-
- name: Comment PR with MCP-Eval report
64-
if: ${{ github.event_name == 'pull_request' }}
65-
uses: actions/github-script@v7
66-
env:
67-
REPORT_PATH: ${{ steps.mcpeval.outputs.results-md-path }}
68-
with:
69-
script: |
70-
const fs = require('fs');
71-
const path = process.env.REPORT_PATH;
72-
let body = '<!-- mcpeval-report -->\n';
73-
body += '## MCP-Eval Report\n\n';
74-
try {
75-
const content = fs.readFileSync(path, 'utf8');
76-
body += content;
77-
} catch (e) {
78-
body += '_No report found at ' + path + '_\n';
79-
}
80-
const { owner, repo } = context.repo;
81-
const issue_number = context.issue.number;
82-
83-
// Find existing sticky comment
84-
const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number, per_page: 100 });
85-
const previous = comments.find(c => c.user.type === 'Bot' && c.body.startsWith('<!-- mcpeval-report -->'));
86-
if (previous) {
87-
await github.rest.issues.updateComment({ owner, repo, comment_id: previous.id, body });
88-
} else {
89-
await github.rest.issues.createComment({ owner, repo, issue_number, body });
90-
}
91-
92-
# Optional: Publish the HTML report to GitHub Pages on main/master pushes
93-
pages:
94-
name: Publish Report to Pages
95-
needs: tests
96-
if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') }}
97-
runs-on: ubuntu-latest
14+
call-mcpeval:
15+
uses: lastmile-ai/mcp-eval/.github/workflows/mcpeval-reuseable.yml@main
16+
with:
17+
deploy-pages: true
18+
tests: examples/mcp_server_fetch/tests/test_simple_decorator.py
9819
permissions:
20+
contents: read
9921
pages: write
10022
id-token: write
101-
contents: read
102-
environment:
103-
name: github-pages
104-
url: ${{ steps.deployment.outputs.page_url }}
105-
steps:
106-
- name: Download artifacts
107-
uses: actions/download-artifact@v4
108-
with:
109-
name: mcpeval-artifacts
110-
path: ./mcpeval-artifacts
111-
112-
- name: Prepare site
113-
run: |
114-
set -euo pipefail
115-
mkdir -p site
116-
# Prefer the configured HTML filename; fallback to first HTML we find
117-
if [[ -f "mcpeval-artifacts/mcpeval-reports/mcpeval-results.html" ]]; then
118-
cp mcpeval-artifacts/mcpeval-reports/mcpeval-results.html site/index.html
119-
else
120-
file=$(find mcpeval-artifacts -name "*.html" | head -n 1 || true)
121-
if [[ -n "$file" ]]; then cp "$file" site/index.html; else echo '<h1>No report available</h1>' > site/index.html; fi
122-
fi
123-
# Include badges if available
124-
if [[ -d "mcpeval-artifacts/mcpeval-reports/badges" ]]; then
125-
mkdir -p site/badges
126-
cp -r mcpeval-artifacts/mcpeval-reports/badges/*.svg site/badges/ || true
127-
fi
128-
129-
- name: Upload Pages artifact
130-
uses: actions/upload-pages-artifact@v3
131-
with:
132-
path: ./site
133-
134-
- name: Deploy to GitHub Pages
135-
id: deployment
136-
uses: actions/deploy-pages@v4
137-
138-
23+
pull-requests: write
24+
secrets: inherit

0 commit comments

Comments
 (0)