Phase 4 distribution audit: server-card.json + auth-wall findings

- Add /.well-known/mcp/server-card.json (Smithery auto-scan endpoint)
- Sitemap.xml now lists both well-known endpoints
- JOURNAL.md: full audit of marketplace auth walls (mcpservers.org=$39, mcp.so/smithery/glama=login)
- JAOUAD_TODO.md: updated with concrete copy-paste instructions for the 3 directories
- STATE.md: phase 4 marked blocked by auth walls until human steps in
- deploy/inspect_*.py + submit_mcpservers.py: playwright probes (kept for re-runs)
This commit is contained in:
Kaouani Jaouad 2026-04-13 11:57:32 +02:00
parent c08339e547
commit a12081e536
9 changed files with 262 additions and 15 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ data/.wallet_pass
data/.vps_secrets
__pycache__/
*.pyc
data/*.html

View File

@ -20,20 +20,21 @@ Classé par impact revenu décroissant.
---
### 2. Soumettre Aegis402 sur mcpservers.org
**Pourquoi** : 440+ MCP servers déjà listés là-bas, c'est LA place de marché alternative à lobehub. Submission gratuite.
### 2. ~~Soumettre Aegis402 sur mcpservers.org~~ — **PAYANT 39 $, skip**
**Vérifié 2026-04-13 (Playwright)** : le formulaire affiche "Submit & Pay ($39)", hidden field `plan=premium`. Pas un bon ROI bootstrap, mieux vaut mettre les 39 $ dans le wallet.
**Comment** :
1. Aller sur https://mcpservers.org/submit
2. Remplir le formulaire :
- **Server Name** : `Aegis402`
- **Short Description** : `Pay-per-call CVE intel for AI agent dependencies — scans GHSA + CISA KEV, x402 native, USDC on Base, no signup.`
- **Link** : `https://aegis402.vmaxbadge.ch/`
- **Category** : Security (ou Development)
- **Email** : `contact@vmaxbadge.ch` (ou autre)
3. Envoyer
### 2bis. Soumettre Aegis402 sur mcp.so + smithery.ai + glama.ai
**Pourquoi** : 3 directories majeures (mcp.so = 19 969 servers, glama.ai = 21 313, smithery = scan auto). Toutes gratuites mais auth-walled (sign-in obligatoire pour soumettre). Je ne peux pas créer de compte humain.
**Coût** : 0 € • **Temps** : 3 min • **ETA review** : 1-7 jours
**Comment** (3 sites, ~10 min total une fois loggué) :
1. https://mcp.so/submit (Sign In via GitHub) → Type=Server, Name=Aegis402, URL=https://aegis402.vmaxbadge.ch/, Server Config = `{"transport":"http","url":"https://aegis402.vmaxbadge.ch/scan"}`
2. https://glama.ai/mcp/servers (Sign Up requis) → bouton "Add Server" en haut
3. https://smithery.ai/new (Sign In) → coller URL `https://aegis402.vmaxbadge.ch/`. Smithery scrape auto le `/.well-known/mcp/server-card.json` que j'ai déjà ajouté.
**Description copy-paste** :
> Pay-per-call CVE intel for AI agent dependencies — scans GHSA + CISA KEV, x402 native, USDC on Base, no signup.
**Coût** : 0 € • **Temps** : 10 min • **ETA review** : 1-7 jours
---

View File

@ -168,3 +168,42 @@ ssh -i ~/.ssh/vmax-badge-vps ubuntu@83.228.222.28 \
- Bottleneck #1 : 10€ USDC pour bootstrap discovery via 1 self-paiement
- Bottleneck #2 : un click sur mcpservers.org/submit + lobehub
- Bottleneck #3 : compte GitHub pour repo public + PR awesome lists
## 2026-04-13 — mcpservers.org = paywall $39
- Inspecté form via Playwright headless
- Bouton "Submit & Pay ($39)" — submission n'est PAS gratuite, hidden plan=premium
- Décision : SKIP. Capital initial = 0 €, pas question de cramer 39 € pour un listing incertain
- Pivot vers alternatives gratuites : mcp.so, smithery.ai, glama.ai, awesome-mcp-servers (PR GitHub)
- Mise à jour JAOUAD_TODO.md : retirer mcpservers.org, le marquer "payant, pas rentable bootstrap"
## 2026-04-13 — Phase 4 distribution : audit auth walls
Tentatives autonomes de soumission marketplace (Playwright headless) :
- mcpservers.org : **payant 39 $** (bouton "Submit & Pay"). Skip — capital 0 €.
- smithery.ai/new : **Vercel security checkpoint** (anti-bot CAPTCHA). Bloqué headless.
- mcp.so/submit : **Sign In requis** (formulaire visible mais POST authentifié).
- glama.ai : "Add Server" requires Sign Up. Pas de soumission anonyme.
- awesome-mcp-servers : PR GitHub → **compte GitHub requis** (cf. JAOUAD_TODO #4).
**Constat honnête** : tous les canaux de distribution actifs sont auth-walled
(login OAuth ou paiement). Aucun ne peut être franchi par un agent sans
identité humaine. C'est une caractéristique structurelle du marché 2026, pas
un bug d'implémentation.
**Pivot autonome** : maximiser la découvrabilité crawler-side, puisque je ne
peux pas pousser, je dois être trouvé.
- Ajout endpoint `/.well-known/mcp/server-card.json` (auto-scan Smithery)
- Sitemap.xml mis à jour avec les deux well-known
- Déployé en prod, vérifié 200 OK
- Schema.org WebAPI déjà en place dans la landing
- robots.txt déjà allow-all + sitemap référencé
**Ce qui reste vraiment bloquant pour Jaouad** (cf. JAOUAD_TODO.md) :
1. Funder wallet (10 € → bootstrap x402 Bazaar discovery)
2. Créer compte GitHub `aegis402` → push + PR awesome-mcp-servers
3. Soumettre formulaires sign-in (mcp.so, glama, smithery) une fois logué
4. Post HN/Reddit le jour J
Sans humain, je suis un service indexable mais non-promu. Le service marche,
le code est propre, la prod est stable. La friction est 100% côté distribution.

View File

@ -11,8 +11,11 @@ Avant chaque dépense de capital → projection complète, sinon je code en grat
**Phase 2 — Productionisation gratuite** ✅ TERMINÉE
**Phase 3 — Déploiement production** ✅ LIVE depuis 2026-04-13
→ https://aegis402.vmaxbadge.ch (cohabite avec VMAX, zéro impact, prêt 0 €)
**Phase 4 — Distribution** 🟢 EN COURS
→ soumissions marketplaces, post HN, monitoring revenu
**Phase 4 — Distribution** 🟡 BLOQUÉE PAR AUTH WALLS
→ marketplaces (mcp.so, smithery, glama, mcpservers.org) toutes gated login/payant
→ audit complet 2026-04-13 dans JOURNAL.md
→ pivot autonome : `/.well-known/mcp/server-card.json` ajouté pour auto-scan crawler
→ la suite nécessite Jaouad (cf. JAOUAD_TODO.md, surtout #1 wallet + #4 GitHub)
## Production live
- URL : https://aegis402.vmaxbadge.ch

28
deploy/inspect_glama.py Normal file
View File

@ -0,0 +1,28 @@
"""Inspect glama.ai Add Server flow."""
from playwright.sync_api import sync_playwright
from pathlib import Path
with sync_playwright() as p:
b = p.chromium.launch(headless=True)
ctx = b.new_context(user_agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36")
page = ctx.new_page()
page.goto("https://glama.ai/mcp/servers", wait_until="domcontentloaded", timeout=30000)
# find Add Server link
link = page.query_selector('a:has-text("Add Server")')
if link:
href = link.get_attribute("href")
print("ADD SERVER HREF:", href)
if href.startswith("/"):
href = "https://glama.ai" + href
page.goto(href, wait_until="domcontentloaded", timeout=30000)
print("--- ADD PAGE ---")
print("URL:", page.url)
print("TITLE:", page.title())
print(page.inner_text("body")[:1500])
Path("data/glama_add.html").write_text(page.content())
# any forms?
for inp in page.query_selector_all("input"):
print(" INPUT:", inp.get_attribute("name"), inp.get_attribute("type"), inp.get_attribute("placeholder"))
else:
print("No Add Server link found")
b.close()

21
deploy/inspect_mcpso.py Normal file
View File

@ -0,0 +1,21 @@
"""Inspect mcp.so submission flow."""
from playwright.sync_api import sync_playwright
from pathlib import Path
OUT = Path(__file__).resolve().parent.parent / "data" / "mcpso.html"
with sync_playwright() as p:
b = p.chromium.launch(headless=True)
ctx = b.new_context(user_agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36")
page = ctx.new_page()
for url in ("https://mcp.so/submit", "https://mcp.so/", "https://glama.ai/mcp/servers"):
try:
page.goto(url, wait_until="domcontentloaded", timeout=30000)
print(f"--- {url} -> {page.url}")
print("TITLE:", page.title())
txt = page.inner_text("body")[:600]
print(txt)
print()
except Exception as e:
print(f"FAIL {url}: {e}")
b.close()

View File

@ -0,0 +1,29 @@
"""Inspect smithery.ai/new submission form."""
from playwright.sync_api import sync_playwright
from pathlib import Path
OUT = Path(__file__).resolve().parent.parent / "data" / "smithery_new.html"
with sync_playwright() as p:
b = p.chromium.launch(headless=True)
ctx = b.new_context(user_agent="Mozilla/5.0 Aegis402-bot/0.1")
page = ctx.new_page()
page.goto("https://smithery.ai/new", wait_until="networkidle", timeout=45000)
OUT.write_text(page.content())
print("URL:", page.url)
print("TITLE:", page.title())
# capture all forms / inputs
inputs = page.query_selector_all("input")
print(f"INPUTS: {len(inputs)}")
for i in inputs:
print(" -", i.get_attribute("name"), i.get_attribute("type"), i.get_attribute("placeholder"))
btns = page.query_selector_all("button")
print(f"BUTTONS: {len(btns)}")
for bt in btns:
t = bt.inner_text().strip()
if t:
print(" -", t)
# body text first 1500 chars
print("---BODY---")
print(page.inner_text("body")[:1500])
b.close()

102
deploy/submit_mcpservers.py Normal file
View File

@ -0,0 +1,102 @@
"""Submit Aegis402 to mcpservers.org via Playwright headless chromium.
Form spec (inspected previously):
GET https://mcpservers.org/submit
fields: name, description, url, category(select), email, terms(checkbox)
"""
import sys
from pathlib import Path
from playwright.sync_api import sync_playwright
OUT = Path(__file__).resolve().parent.parent / "data" / "mcpservers_submit.html"
OUT.parent.mkdir(exist_ok=True)
PAYLOAD = {
"name": "Aegis402",
"description": "Pay-per-call CVE intel for AI agent dependencies — scans GHSA + CISA KEV, x402 native, USDC on Base, no signup.",
"url": "https://aegis402.vmaxbadge.ch/",
"email": "contact@vmaxbadge.ch",
}
def main() -> int:
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
ctx = browser.new_context(user_agent="Mozilla/5.0 Aegis402-bot/0.1 (+https://aegis402.vmaxbadge.ch/)")
page = ctx.new_page()
try:
page.goto("https://mcpservers.org/submit", wait_until="networkidle", timeout=30000)
except Exception as e:
print(f"FAIL goto: {e}")
return 2
try:
page.fill('input[name="name"]', PAYLOAD["name"])
page.fill('input[name="description"], textarea[name="description"]', PAYLOAD["description"])
page.fill('input[name="url"]', PAYLOAD["url"])
page.fill('input[name="email"]', PAYLOAD["email"])
except Exception as e:
print(f"FAIL fill: {e}")
OUT.write_text(page.content())
return 3
# category select — try Security first, fallback Development
try:
sel = page.query_selector('select[name="category"]')
if sel:
opts = [o.inner_text().strip() for o in sel.query_selector_all("option")]
print("CATEGORY OPTIONS:", opts)
pick = None
for cand in ("Security", "Developer Tools", "Development", "Tools"):
for o in opts:
if cand.lower() in o.lower():
pick = o
break
if pick:
break
if pick:
page.select_option('select[name="category"]', label=pick)
print("PICKED:", pick)
except Exception as e:
print(f"WARN category: {e}")
# checkbox(es)
try:
for cb in page.query_selector_all('input[type="checkbox"]'):
if not cb.is_checked():
cb.check()
except Exception as e:
print(f"WARN checkbox: {e}")
OUT.write_text(page.content())
print(f"PRE-SUBMIT html saved to {OUT}")
# find submit button
try:
btn = page.query_selector('button[type="submit"], input[type="submit"]')
if not btn:
print("FAIL: no submit button found")
return 4
btn.click()
page.wait_for_load_state("networkidle", timeout=20000)
except Exception as e:
print(f"FAIL submit: {e}")
OUT.write_text(page.content())
return 5
post = Path(__file__).resolve().parent.parent / "data" / "mcpservers_response.html"
post.write_text(page.content())
print(f"POST-SUBMIT html: {post}")
print(f"FINAL URL: {page.url}")
# quick success heuristic
text = page.inner_text("body").lower()
for kw in ("thank", "received", "submitted", "success", "review"):
if kw in text:
print(f"SUCCESS keyword found: {kw}")
return 0
print("NO success keyword — inspect HTML manually")
return 1
if __name__ == "__main__":
sys.exit(main())

View File

@ -127,7 +127,7 @@ def robots():
@app.get("/sitemap.xml", response_class=PlainTextResponse)
def sitemap():
base = "https://aegis402.vmaxbadge.ch"
urls = ["/", "/mcp", "/health", "/payment"]
urls = ["/", "/mcp", "/health", "/payment", "/.well-known/mcp.json", "/.well-known/mcp/server-card.json"]
body = '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
for u in urls:
body += f" <url><loc>{base}{u}</loc><changefreq>hourly</changefreq></url>\n"
@ -142,6 +142,29 @@ def well_known_mcp(request: Request):
return mcp_manifest(public_url=public_url)
@app.get("/.well-known/mcp/server-card.json")
def well_known_server_card(request: Request):
"""Smithery auto-scan fallback. When automatic capability detection fails,
Smithery reads this file. We expose a compact card with capabilities + price."""
public_url = (os.environ.get("AEGIS402_PUBLIC_URL") or str(request.base_url)).rstrip("/")
m = mcp_manifest(public_url=public_url + "/")
return {
"name": m["name"],
"displayName": m["displayName"],
"description": m["description"],
"version": m["version"],
"homepage": public_url + "/",
"transport": {"type": "http", "url": public_url + "/scan"},
"capabilities": {
"tools": [{"name": t["name"], "description": t["description"]} for t in m["tools"]],
},
"auth": {"type": "x402", "asset": "USDC", "network": "base"},
"pricing": m["payment"]["pricing"],
"categories": ["security", "developer-tools", "vulnerability-scanning"],
"tags": ["mcp", "x402", "cve", "ghsa", "kev", "agent-tools"],
}
@app.get("/health")
def health():
conn = get_conn()