From 12f819b1e95670412e18cefec4aafd56bb826638 Mon Sep 17 00:00:00 2001 From: MaheshtheDev <38828053+MaheshtheDev@users.noreply.github.com> Date: Mon, 26 Oct 2026 20:40:42 +0000 Subject: [PATCH] feat(mcp): serve an MCP Server Card at /mcp/server-card Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- apps/mcp/e2e/auth.test.ts | 22 +++++++++++++++++++ apps/mcp/src/server/index.ts | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/apps/mcp/e2e/auth.test.ts b/apps/mcp/e2e/auth.test.ts index d4cf0da1..3487db6a 100644 --- a/apps/mcp/e2e/auth.test.ts +++ b/apps/mcp/e2e/auth.test.ts @@ -42,6 +42,28 @@ describe("MCP — transport & auth (raw HTTP)", () => { expect(body.authorization_servers?.length).toBeGreaterThan(0) }) + it("serves an MCP Server Card at the reserved location", async () => { + const res = await fetch(`${MCP_URL}/server-card`, { + headers: { Accept: "application/mcp-server-card+json" }, + }) + expect(res.status).toBe(200) + expect(res.headers.get("content-type")).toBe( + "application/mcp-server-card+json", + ) + expect(res.headers.get("access-control-allow-origin")).toBe("*") + const card = (await res.json()) as { + $schema?: string + name?: string + remotes?: { type?: string; url?: string }[] + } + expect(card.$schema).toBe( + "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json", + ) + expect(card.name).toMatch(/^[a-zA-Z0-9.-]+\/[a-zA-Z0-9._-]+$/) + expect(card.remotes?.[0]).toMatchObject({ type: "streamable-http" }) + expect(card.remotes?.[0]?.url).toMatch(/\/mcp$/) + }) + it("rejects a request with no token (401 + WWW-Authenticate)", async () => { const res = await fetch(MCP_URL, { method: "POST", diff --git a/apps/mcp/src/server/index.ts b/apps/mcp/src/server/index.ts index eef351c2..9ebddff2 100644 --- a/apps/mcp/src/server/index.ts +++ b/apps/mcp/src/server/index.ts @@ -2,6 +2,7 @@ import type { AuthInfo } from "@modelcontextprotocol/server" import { createMcpHandler } from "agents/mcp/server" import { Hono, type Context } from "hono" import { cors } from "hono/cors" +import { etag } from "hono/etag" import { isApiKey, TransientAuthError, @@ -77,6 +78,47 @@ function resourceMetadata(c: Context<{ Bindings: Bindings }>) { app.get("/.well-known/oauth-protected-resource", resourceMetadata) app.get(PROTECTED_RESOURCE_METADATA_PATH, resourceMetadata) +app.get("/mcp/server-card", etag(), (c) => { + const card = { + $schema: + "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json", + name: "ai.supermemory/memory", + version: "1.0.0", + title: "Supermemory", + description: + "Save, search, and recall a user's memories and documents across AI conversations.", + websiteUrl: "https://supermemory.ai/docs/supermemory-mcp/mcp", + repository: { + url: "https://github.com/supermemoryai/supermemory", + source: "github", + subfolder: "apps/mcp", + }, + icons: [ + { + src: "https://supermemory.ai/icon-512.png", + mimeType: "image/png", + sizes: ["512x512"], + }, + { + src: "https://supermemory.ai/favicon.svg", + mimeType: "image/svg+xml", + sizes: ["any"], + }, + ], + remotes: [ + { + type: "streamable-http", + url: c.env.MCP_RESOURCE || DEFAULT_MCP_RESOURCE, + }, + ], + } + + return c.body(JSON.stringify(card, null, 2), 200, { + "Content-Type": "application/mcp-server-card+json", + "Cache-Control": "public, max-age=3600", + }) +}) + app.get("/.well-known/openai-apps-challenge", (c) => { return c.text(c.env.OPENAI_APPS_CHALLENGE || "") })