MCP server for Flagyard — list training/competitive labs, open challenges, spawn/stop instances, download challenge files, submit flags, and read profile/leaderboard stats.
Built for Hermes Agent (stdio + FastMCP), matching the style of the HTB MCP integration.
Flagyard uses Tuwaiq SSO / Keycloak (realm=main, client_id=flagyard).
Password grant works:
export FLAGYARD_USERNAME='your_user'
export FLAGYARD_PASSWORD='your_pass'Or provide a bearer token:
export FLAGYARD_ACCESS_TOKEN='eyJ...'
export FLAGYARD_REFRESH_TOKEN='eyJ...' # optionalpython3 /root/flagyard-mcp-server/flagyard_mcp.pymcp_servers:
flagyard:
command: python3
args:
- /root/flagyard-mcp-server/flagyard_mcp.py
enabled: true
timeout: 180
env:
FLAGYARD_USERNAME: "YOUR_USERNAME"
FLAGYARD_PASSWORD: "..."Restart Hermes after editing config so tools are re-discovered (mcp_flagyard_*).
| Tool | Purpose |
|---|---|
get_server_status |
SSO + API health, current user summary |
list_labs |
Public labs (training / competitive) |
get_lab |
Lab details + challenge list |
list_challenge_categories |
Category catalog |
get_challenge |
Full challenge details + running instance |
start_instance |
Spawn instance, poll for URL |
stop_instance |
Destroy instance |
get_instance |
Current instance for a challenge |
list_challenge_files |
Signed download URLs |
download_challenge_files |
Save attachments to disk |
submit_flag |
Submit flag |
get_profile |
Overview stats (points/rank/level) |
get_profile_activities |
Solve history |
get_profile_badges |
Badges |
get_leaderboard |
Global leaderboard |
get_online_players |
Online count |
get_latest_challenges |
Dashboard latest |
get_latest_activity |
Live solve feed |
search_challenges |
Name search across labs |
Base: https://api.flagyard.com/api
Auth header: Authorization: Bearer <jwt>
| Action | Method | Path |
|---|---|---|
| Labs list | GET | /labs/public?type=training|competitive |
| Lab + challenges | GET | /labs/{labId}/public |
| Challenge details | GET | /labs/{labId}/challenges/{challengeId} |
| Start instance | POST | /labs/{labId}/challenges/{challengeId}/instance |
| Stop instance | DELETE | /labs/{labId}/challenges/{challengeId}/instance |
| Challenge files | GET | /labs/{labId}/challenges/{challengeId}/challenge-files |
| Submit flag | POST | /labs/{labId}/challenges/{challengeId}/flag body {"flag":"..."} |
| Profile overview | GET | /users/profile/overview |
| Activities | GET | /users/profile/activities |
| Badges | GET | /users/profile/badges |
| Leaderboard | GET | /users/leaderboard |
| Account status | GET | /users/current/status |
Instance URL appears on challenge details as
data.currentRunningInstanceForUser.instanceAddress
(e.g. http://xxxx-0.playat.flagyard.com).
Instance payloads from the API may include a correctFlag field. This MCP redacts it by default. Set FLAGYARD_INCLUDE_INSTANCE_FLAGS=true only if you intentionally want raw API payloads.
list_labs(lab_type="training")→ pick lab id (Web = 2)get_lab(2)→ pick challenge UUIDget_challenge(2, "<uuid>")→ description, points,hasChallengeFiles- If files:
download_challenge_files(2, "<uuid>") - If web/pwn box:
start_instance(2, "<uuid>")→ openinstanceAddress submit_flag(2, "<uuid>", "FlagY{...}")stop_instance(2, "<uuid>")get_profile()/get_profile_activities()for stats