CASE · 082025 — ONGOINGSOLOREGISTRY · A2A ↔ MCP BRIDGE
◦ AGORAHUB · agorahub.dev

Every agent a tool. Every protocol bridged.

Offene Registry, in der AI-Agents sich per agent.json registrieren, entdeckt und verbunden werden. Agents sprechen A2A (Google), werden zur Laufzeit als MCP-Tools exposed (Anthropic). Next.js 16, Prisma 6, Postgres, self-hosted auf Coolify.

§ 01Problem · motivation

Why this exists.

Agents aus verschiedenen Ökosystemen können nicht miteinander sprechen. Jeder Anbieter baut seinen Walled Garden.

Agents aus verschiedenen Ökosystemen — Claude/MCP, Google/A2A, selbstgehostete — können aktuell nicht miteinander sprechen. Jeder Anbieter baut seinen eigenen Walled Garden. Entwickler, die Agents bauen oder konsumieren, brauchen eine neutrale Discovery-Schicht, die bestehende Marketplaces nicht liefern.

GPT Store, Claude MCP Directory — vendor-lock-in, schema-arm, keine protokollübergreifende Execution-Bridge. AgoraHub ist der Versuch, eine Registry zu bauen, in der ein A2A-Agent automatisch als MCP-Tool in Claude Desktop auftaucht, ohne dass der Agent-Builder etwas doppelt schreibt.

§ 02Constraints · operating box

The box it had to fit in.

Solo-Dev, self-hosted, Security-first — weil user-submitted URLs direkt ausgeführt werden.
C/01 · OPS
Solo-Dev, Nebenprojekt. Jede Design-Entscheidung ohne Team wartbar.
C/02 · HOSTING
Self-hosted auf Coolify. Kein Vercel-Lock-in, Infra-Kosten im niedrigen zweistelligen €-Bereich/Monat.
C/03 · STACK
Next.js 16 App Router + Prisma 6 + Postgres. Ein Deployment, keine Microservices.
C/04 · SECURITY
Agents nehmen untrusted JSON von externen Endpoints. XSS-Sanitization, SSRF-Quarantäne, HMAC-Webhooks, API-Keys als SHA-256-Hash.
C/05 · NEUTRAL
Reines A2A + MCP. Keine proprietären Erweiterungen — Agents bleiben portabel.
C/06 · BURSTS
MCP-Tool-Calls kommen in Bursts (LLM probiert parallel). Rate Limiting per IP + per Key.
C/07 · TRUST
Jeder darf publizieren. 4 Trust-Stufen + 5 Badges — automatisch aus Uptime, Completion Rate, Reviews berechnet.
C/08 · NO GATE
Keine manuelle Moderation durch mich. Trust-System muss alleine skalieren.
§ 03Architecture · A2A ↔ MCP bridge

How it runs.

A2A-Agents links → Sicherheitskette (SSRF · Schema-Check · Trust-Gate) → Bridge-Runtime → MCP-Clients rechts. Die Registry ist der Mittelpunkt — jede andere Route geht durch sie.
registry.live·agents 84·trusted 61·mcp-tools 47·ssrf-blocked 12
bridge ms·rl ok
A2A · AGENT 01
translator.v3
agent.json · skills: [translate]
trustS★
A2A · AGENT 02
web-scrape.io
skills: [fetch, parse]
trustG★★
A2A · AGENT 03
pdf-extract
skills: [ocr, extract]
trustB
FILTER · SSRF
DNS + IP gate
no RFC1918 · :443 only
blocked12
FILTER · SCHEMA
compat-check
structural diff
ok91%
FILTER · TRUST ≥ 10
score gate
hysteresis · 30d window
allow47
CORE · BRIDGE
A2A ↔ MCP
agentSkillToMCPTool()
A2AMCPJSON-RPC
bridge/s14
MCP · CLIENT 01
Claude Desktop
/api/mcp/tools
calls/h312
MCP · CLIENT 02
Cursor
tools/call
calls/h178
MCP · CLIENT 03
custom LLM
mcp-sdk
calls/h94
AUDIT LOG · /api/audit · synchronous writes
12:04:18bridgetranslator.v3 → claude-desktop · 142ms
12:04:17ssrfblocked 10.0.0.4 · rfc1918 · synchronous
12:04:16schemadiff: lang:string → language:'de'|'en'
12:04:14trustweb-scrape.io bronze → silver · 30d window
12:04:11mcptools/call · cursor · sql-runner · 87ms
12:04:08a2apdf-extract · skill: ocr · 312 tokens · 1.2s
12:04:05bridgeagentSkillToMCPTool · web-scrape.io → fetch_url
12:04:02ssrfblocked 169.254.169.254 · aws-metadata
12:03:58mcptools/list · claude-desktop · 47 tools · 12ms
12:04:18bridgetranslator.v3 → claude-desktop · 142ms
12:04:17ssrfblocked 10.0.0.4 · rfc1918 · synchronous
12:04:16schemadiff: lang:string → language:'de'|'en'
12:04:14trustweb-scrape.io bronze → silver · 30d window
12:04:11mcptools/call · cursor · sql-runner · 87ms
12:04:08a2apdf-extract · skill: ocr · 312 tokens · 1.2s
12:04:05bridgeagentSkillToMCPTool · web-scrape.io → fetch_url
12:04:02ssrfblocked 169.254.169.254 · aws-metadata
12:03:58mcptools/list · claude-desktop · 47 tools · 12ms
§ 04Decisions · trade-offs

Four deliberate choices.

Pro Entscheidung: was gewählt, statt was, warum.
D/01

MCP-Bridge statt eigenes Protokoll.

chosen
Agents sprechen A2A, werden zur Laufzeit als MCP-Tools exposed via /api/mcp/tools
instead of
Ein eigenes AgoraHub-Protokoll, das Clients erst lernen müssen
reason
Claude Desktop, Cursor und der MCP-SDK-Ecosystem existieren bereits — jeder Agent in der Registry automatisch als MCP-Tool verfügbar zu machen, ist 10× wertvoller als die hundertste Registry mit eigenem REST-Schema. Die Bridge übersetzt A2A-Skill → MCP-Tool-Definition und route't Tool-Calls zurück durch's A2A-Gateway. Adoption ohne Friction.
D/02

Prisma + Postgres statt Vector-DB.

chosen
Handgeschriebener 5-Faktoren-Score: Skill 30 · Schema 25 · Trust 20 · Tags 15 · Activity 10
instead of
pgvector / Embeddings auf Agent-Descriptions
reason
Bei <100 Agents ist Embedding-Similarity overkill und würde Embedding-Kosten + Re-Indexing bei jedem Schema-Update erzeugen. Der handgeschriebene Score ist transparent, debugfähig, und die Gewichtung ist per Commit anpassbar. Upgrade-Pfad auf pgvector bleibt offen, wenn die Registry wirklich in die Tausender wächst — aber dann gegen echte Nutzungsdaten kalibriert, nicht auf Verdacht.
D/03

Next.js Proxy statt Middleware.

chosen
src/proxy.ts — CSP, HSTS, Permissions-Policy, CORS-Allowlist
instead of
middleware.ts im Edge-Runtime
reason
Next.js 16 hat middleware.ts deprecated, und Edge-Runtime-Constraints (kein Node-API-Zugriff) waren für IP-Validierung und HMAC-Verify ohnehin einschränkend. Der neue Proxy-Hook läuft als echter Node-Handler — eine Datei, kein Build-Target-Split, und im Docker-Image auf Coolify funktioniert er identisch wie lokal. Weniger Magic, mehr Kontrolle.
D/04

SSRF-Quarantäne synchron ins AuditLog.

chosen
Synchrones Write vor dem Return — DNS-Resolve → IP-Check → Port-:443-Gate → Log
instead of
Fire-and-forget-Logging parallel zum Request
reason
Agent-Endpoints sind user-submitted URLs — ein böswillig registrierter Agent könnte AWS-Metadata abfragen, interne Services scannen, DNS-Rebinding versuchen. Die Quarantäne-Zählung muss deterministisch sein: wenn ein Agent 3× blockt, fliegt er raus. Async-Logging hätte Race-Conditions erzeugt, bei denen 3 parallele Versuche nur als 1 gezählt werden. Synchron kostet ~2ms zusätzlich und garantiert Count-Integrität.
§ 05Highlights · interesting bits

Things that were not obvious.

Edge-Cases und Details, die erst beim Bauen klar wurden.

Schema-Compatibility mit konkretem Diff

H/01
Wenn Agent A sich mit Agent B verbinden will, vergleicht schema-compatibility.ts deren JSON-Schemas strukturell — required fields, type compatibility, enum overlap. Der User bekommt nicht nur ein Ja/Nein, sondern einen konkreten Diff: "Agent B erwartet language: 'de'|'en', dein Agent sendet lang: string".

Das verhindert die Hälfte aller fehlschlagenden Task-Runs. Ein binärer Check ("incompatible") hätte Developer frustriert, weil sie manuell beide JSON-Schemas öffnen und selbst vergleichen müssten. Diff-Output ist 2 Stunden Arbeit, spart jedem Nutzer 20 Minuten — eine dieser Asymmetrien, die ein Developer-Tool vom funktionalen zum angenehmen machen.

Trust-Auto-Progression mit Hysterese

H/02
Trust-Level sind nicht linear. Ein Agent steigt schneller auf (Bronze → Silver bei 10 erfolgreichen Tasks) als er absteigt (erst bei >20% Failure-Rate über 30 Tage).

Sonst würden einzelne Timeouts bei fremden Endpoints gute Agents sofort demoten — und das passiert ständig, wenn der Agent gegen flaky Third-Party-APIs läuft. MCP-Tools werden nur ab Trust-Score ≥ 10 exposed, damit Claude-User vor Müll geschützt sind, ohne dass ich manuell moderieren muss. Die Schwelle ist der einzige menschliche Touchpoint im ganzen Trust-System.

API-Keys als Hash + Prefix

H/03
Beim Key-Create wird der Plaintext-Key einmal angezeigt (URL-Query newKey). In der DB liegen nur SHA-256-Hash + 8-Zeichen-Prefix. Auth-Lookup: Request kommt mit Key → Prefix extrahieren → Index-Hit auf keyPrefix → Hash-Vergleich.

Keine Fulltable-Scans (der Prefix ist indiziert und selten genug, um <5 Kandidaten zu treffen), und ein DB-Leak gibt keinem Angreifer funktionierende Credentials. Klassisches GitHub-Pattern, aber überraschend viele Registries speichern Keys heute noch plaintext oder verschlüsselt-aber-dekryptierbar.

SSRF-Fallback über alle IPs

H/04
DNS-Resolve kann mehrere A/AAAA-Records zurückgeben — wenn nur einer in RFC1918 fällt, ist die ganze Domain suspekt. Umgekehrt: wenn eine IP valide ist (public, nicht loopback), darf der Request dort hin, auch wenn andere IPs im DNS-Pool RFC1918 sind.

Der Scanner validiert jede IP einzeln und wählt die erste valide für den Call. DNS-Rebinding-Angriffe (die zwischen Resolve und Connect die IP wechseln) werden blockiert, weil der HTTP-Client an die validierte IP pin't, nicht an den Hostname. Ein zehnzeiliger Fix, der eine ganze Angriffsklasse schließt.
§ 06Stack · in production

What's running.

Next.js 16 · App RouterTypeScript 5Prisma 6 · PostgresTailwind · shadcnA2A (Google)MCP (Anthropic)HMAC webhooksSHA-256 key hashingIP allowlist · SSRF gateCoolify · self-hostedRate limit · per IP + keyAudit log · synchronous
§ 07Reflection · takeaways

What I learned.

Projekt läuft öffentlich. Diese Dinge nehme ich mit.

Bridges schlagen Protokolle.

Ich hätte ein eigenes Agent-Protokoll definieren können, das "besser" als A2A und MCP ist. Realität: jeder Client, den Nutzer schon haben, spricht eins der beiden. Ein drittes Protokoll zu zwingen wäre eine Adoption-Mauer gewesen. Die Bridge-Strategie nimmt die Frage "welches Protokoll gewinnt" vom Tisch — beide gewinnen, AgoraHub ist der Übersetzer. Das ist langweiliger als ein eigenes Protokoll zu designen, aber es ist der Grund, warum der erste Claude-User in Minuten produktiv war.

User-submitted URLs sind ein Angriffs-Primitiv.

Jeder registrierte Agent liefert eine URL, die mein Server aufrufen soll. Das ist aus Angreifersicht exakt die Definition von SSRF. Ich habe die Sicherheitskette (DNS-Gate, IP-Validation, Port-Erzwingung, synchrone Audit-Logs) vor die erste echte Integration gesetzt — nicht nachdem der erste böswillige Agent versucht hat, AWS-Metadata abzufragen. Bei Tools, die User-URLs callen, ist Security-Engineering kein Feature, es ist das Fundament. Wer das als Feature nachrüstet, rüstet zu spät.

◦ NEXT CASE · 09 / 11
Flashbuddy
← alle Projekte