mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-27 01:13:26 +00:00
<!-- VORFLUX_AGENT_PR_BODY_BEGIN -->
Adds the full 654-entry MCP directory without bundling records into client JavaScript, with explicit capability status and connector branding that degrades safely when no authoritative logo is available.
## Changes
- Lazy-load and validate the searchable, filterable, progressively rendered MCP catalog.
- Render same-origin proxied provider icons for 543 entries, with a reviewed domain allowlist and deterministic fallback marks for 111 unresolved or unbranded entries.
- Record OAuth discovery capability separately from end-to-end support; all directory setup actions remain suppressed until their authentication flow is verified.
- Add a reproducible OAuth metadata probe with HTTPS/private-network protections, stable URL keys, authorization-server scanning, and catalog fingerprint validation.
- Add Google Drive branding for the curated built-in connector.
## Testing
- **Passed:** Deterministic generation and catalog assertions.
```bash
PATH="$HOME/.bun/bin:$PATH" python3 apps/web/scripts/generate-mcp-directory.py --output
cmp apps/web/public/mcp-directory.json
```
Verified 654 entries, 254 DCR discoveries, 27 preregistered OAuth discoveries, 373 unclassified entries, and zero directory setup actions.
- **Passed:** Stale OAuth metadata fingerprint is rejected by the generator.
- **Passed:** Touched-file Biome checks and `git diff --check`.
- **Passed:** Icon proxy returned 200 for an allowlisted domain and 400 for an unknown valid-looking domain.
- **Passed:** Authenticated desktop/mobile browser inspection and conservative capability labels.
- **Passed:** Public preview returned HTTP 200 and rendered the real app. Authentication cookies do not transfer to the public hostname, so the public screenshot shows login.
- **Partial:** Repository-wide TypeScript checks remain blocked by unrelated existing errors outside the touched MCP files.
- **Partial:** 111 entries intentionally retain deterministic fallback marks; endpoint-derived domains may not always be the canonical brand logo.
- **Blocked:** Google rejected the local HTTP OAuth callback, so live Google Drive consent, callback, persistence, tool discovery, disconnect, and reconnect were not completed.
Public preview: https://ar8ruchhbi65.preview.us1.vorflux.com/configure/tools
---
**Attached Images**
*[288.csv]*
*[mcp-directory-final.json]*



<!-- VORFLUX_AGENT_PR_BODY_END -->
---
**Session Details**
- Session: [View Session](https://supermemory.us1.vorflux.com/agent-sessions/1cd0aab9-2a45-4818-aa13-f9bfe032ddba)
- Requested by: Dhravya Shah (dhravya@supermemory.com)
- Address comments on this PR. Add `(aside)` to your comment to have me ignore it.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes how users pick MCP URLs and auth (OAuth vs API key) before hitting existing connect endpoints; no new backend auth logic in this diff, but misconfiguration or trusting bad URLs remains a user-risk surface.
>
> **Overview**
> Adds a **browseable MCP directory** on the Company Brain connectors page: the catalog is **not bundled in JS**—it loads from static **`/mcp-directory.json`** only after the user opens the directory (with validation, caching, and abort handling).
>
> The new **`McpDirectoryBrowser`** supports search, category/availability filters, and progressive “show more” rendering. Supported remote entries route into the existing custom MCP flow via **Set up**, which pre-fills name/URL and opens the connector dialog with context-specific copy.
>
> The custom connector dialog now uses an explicit **OAuth vs API key** toggle; API key fields only appear for API-key mode, and directory-backed connections get **stable slugs** (`-dir-` suffix) so names display cleanly on connected cards. **Middleware** excludes `mcp-directory.json` from the auth matcher so the asset can be fetched publicly.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 8b59bae84a. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
import { type NextRequest, NextResponse } from "next/server"
|
|
import iconDomains from "@/lib/mcp-icon-domains.json"
|
|
|
|
const DOMAIN_RE =
|
|
/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i
|
|
const MAX_ICON_BYTES = 256 * 1024
|
|
|
|
const ALLOWED_DOMAINS = new Set(iconDomains.domains)
|
|
|
|
export async function GET(request: NextRequest) {
|
|
const domain = request.nextUrl.searchParams
|
|
.get("domain")
|
|
?.trim()
|
|
.toLowerCase()
|
|
if (!domain || !DOMAIN_RE.test(domain) || !ALLOWED_DOMAINS.has(domain)) {
|
|
return new NextResponse(null, { status: 400 })
|
|
}
|
|
|
|
const response = await fetch(
|
|
`https://www.google.com/s2/favicons?domain=${encodeURIComponent(domain)}&sz=128`,
|
|
{ next: { revalidate: 60 * 60 * 24 * 7 } },
|
|
)
|
|
const contentType = response.headers.get("content-type") ?? ""
|
|
if (!response.ok || !contentType.startsWith("image/")) {
|
|
return new NextResponse(null, { status: 404 })
|
|
}
|
|
const contentLength = Number(response.headers.get("content-length") ?? 0)
|
|
if (contentLength > MAX_ICON_BYTES) {
|
|
return new NextResponse(null, { status: 413 })
|
|
}
|
|
if (!response.body) return new NextResponse(null, { status: 404 })
|
|
const reader = response.body.getReader()
|
|
const chunks: Uint8Array[] = []
|
|
let bytes = 0
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
bytes += value.byteLength
|
|
if (bytes > MAX_ICON_BYTES) {
|
|
await reader.cancel()
|
|
return new NextResponse(null, { status: 413 })
|
|
}
|
|
chunks.push(value)
|
|
}
|
|
const body = new Uint8Array(bytes)
|
|
let offset = 0
|
|
for (const chunk of chunks) {
|
|
body.set(chunk, offset)
|
|
offset += chunk.byteLength
|
|
}
|
|
return new NextResponse(body, {
|
|
headers: {
|
|
"cache-control":
|
|
"public, max-age=86400, s-maxage=604800, stale-while-revalidate=2592000",
|
|
"content-type": contentType,
|
|
},
|
|
})
|
|
}
|