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:
parent
c08339e547
commit
a12081e536
|
|
@ -5,3 +5,4 @@ data/.wallet_pass
|
|||
data/.vps_secrets
|
||||
__pycache__/
|
||||
*.pyc
|
||||
data/*.html
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
39
JOURNAL.md
39
JOURNAL.md
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
7
STATE.md
7
STATE.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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())
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue