diff --git a/apps/web/src/app/api/plugins/route.ts b/apps/web/src/app/api/plugins/route.ts
new file mode 100644
index 0000000..f96c0e9
--- /dev/null
+++ b/apps/web/src/app/api/plugins/route.ts
@@ -0,0 +1,33 @@
+/**
+ * JSON plugin listing at https://c0mpute.com/api/plugins.
+ *
+ * The marketplace page (`/plugins`) renders from `loadAllPlugins()` at build
+ * time and has no runtime data source. WebMCP tools run client-side, though, so
+ * they need a fetchable endpoint to enumerate the plugin catalogue for an agent.
+ * This route exposes the same manifest data as compact JSON.
+ */
+
+import { NextResponse } from "next/server";
+import { loadAllPlugins, tagline, installCommand } from "@/lib/plugins";
+
+export function GET() {
+ const plugins = loadAllPlugins().map((p) => ({
+ id: p.id,
+ name: p.name,
+ version: p.version,
+ kind: p.kind,
+ tagline: tagline(p),
+ description: p.description,
+ keywords: p.keywords ?? [],
+ homepage: p.homepage,
+ source: p.source,
+ install_command: installCommand(p),
+ install_url: `https://c0mpute.com/plugins/${p.id}/install.sh`,
+ web: `https://c0mpute.com/plugins/${p.id}`,
+ }));
+
+ return NextResponse.json(
+ { count: plugins.length, plugins },
+ { headers: { "cache-control": "public, max-age=300, s-maxage=300" } },
+ );
+}
diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx
index 310c647..ebfa2a4 100644
--- a/apps/web/src/app/layout.tsx
+++ b/apps/web/src/app/layout.tsx
@@ -2,6 +2,7 @@ import type { Metadata, Viewport } from "next";
import Link from "next/link";
import "./globals.css";
import Script from "next/script";
+import { WebMcpProvider } from "@/components/webmcp-provider";
const SITE = "https://c0mpute.com";
const DESCRIPTION =
@@ -43,6 +44,7 @@ export default function RootLayout({
return (
+