buildmymcpserver/apps/web/app/templates/page.tsx

317 lines
12 KiB
TypeScript
Raw Normal View History

feat(marketplace): template publish + fork + voting/ranking + admin moderation What this enables: - A user builds an MCP server. If others would benefit, they click 'Publish as template' on their server detail page. The spec + pre-rendered TypeScript snapshot is preserved. - Visitors browse /templates, filter by category, sort by trending/top/newest. Each template card shows fork count + active deployment count as natural manipulation-resistant popularity signal. - /templates/[slug] shows the full plan: tool list with input schemas, required-credential explanations (with 'how to get one' deep links), and a collapsible code preview so users can audit before forking. - Fork is one click → /servers/new?template=slug. The wizard skips Step 1 and pre-fills Step 2 with the template's parsed spec. Forker only fills in their own credentials. mcp_servers.template_id is recorded; template.fork_count is bumped atomically. Each fork gets its own isolated container with its own port, its own AES-256 secrets — the template author has zero visibility into the fork's traffic or data. - Admin /admin/templates moderation: verify quality templates (shows shield badge in marketplace), hide low-effort ones, takedown anything malicious. Takedowns cascade-pause every fork container — owners must re-deploy. Why template+fork instead of shared-container: - Shared containers would mean the publisher's quota + their secrets + their logs are exposed to forkers. Bad ergonomics, bad security, bad ownership. - Templates/forks decouple the spec (shared, vouched-for) from the runtime (isolated per user). Network-effect moat without the trust collapse. Why no 5-star voting in v1: - Manipulation-anfällig, empty lists without adoption. We use fork count + active deploys + verified badge. Trending algorithm: score = (activeDeploys * 3 + forks) / sqrt(ageDays + 1) Real signal, no brigading attack surface. Backend: - New schema: templates table (16 cols incl. tools_schema, generated_code, required_secrets, allowedDomains, status enum, verified, fork_count). - mcp_servers.template_id FK + idx for fork lookup. - @bmm/types: SpecEdit unchanged, CreateServerInput accepts optional templateId. - preview-cache.ts: new cachePrebuiltCode/loadPrebuiltCode for storing the template's full rendered server.ts alongside the spec. Generator worker detects this and skips the render step — uses the audited pre-built code verbatim. Banned-pattern re-scan at publish time. - routes/templates.ts: 5 public/auth routes + 2 admin routes. Banned-pattern re-scan before publish. Slug auto-uniqued. forkCount atomic-increment via SQL. UI: - /templates marketplace with trending/top/newest tabs, category filter, search. Cards show forks + live count + author + verified badge. - /templates/[slug] full detail with tools, credentials-with-hints, expandable code preview, fork CTA, ownership + stats sidebar, 'forking is safe' explainer. - /servers/new?template=slug — wizard auto-jumps to Step 2 with template spec pre-filled, fork banner at top with link back to template. - /servers/[id] new Publish tab with title, category, descriptions, per-secret hint fields (description + howToGetUrl per UPPER_SNAKE_CASE key). - /admin/templates moderation with verify/hide/takedown actions. - Marketing nav now includes /templates. Verified end-to-end: - Published Echo Demo Template from marco@test.local's live server - Marketplace lists it correctly with stats - Detail page renders with all sections - Fork CTA navigates to wizard with ?template= param - Wizard skips Step 1, shows fork banner, pre-fills spec - Build succeeds in ~10s (cached spec + prebuilt code path skips Claude AND render), container live on :4109 with proper OAuth 401 → token → 200 flow - DB: templates.fork_count=1, activeDeployments=1, mcp_servers.template_id populated on the fork - /admin/templates shows the new template with verify/hide/takedown controls
2026-05-19 23:22:35 +02:00
'use client';
import { useEffect, useState } from 'react';
import Link from 'next/link';
import { ShieldCheck, GitFork, Activity } from 'lucide-react';
import { apiFetch } from '@/lib/api';
import { cn } from '@/lib/cn';
import { Logo } from '@/components/logo';
import { Input } from '@/components/input';
interface Template {
id: string;
slug: string;
title: string;
shortDescription: string;
category: string;
feat(marketplace): dashboard nav link + My-templates filter The logged-in user can now reach the marketplace and filter to their own templates. Dashboard nav: - Added 'Marketplace' item (Overview · Servers · Marketplace · Audit · Settings). /templates page — login-aware: - Detects session via /v1/auth/me. Logged-in users get a 'Dashboard' + '+ New server' header instead of 'Home' + 'Start building'. - New [All templates | My templates] scope toggle, shown only when logged in. - 'My templates' loads GET /v1/templates/mine and shows EVERY status the user owns (public / hidden / draft / takedown) with a colored status badge on each card — so a template you unshared doesn't appear to have vanished. - Sort tabs (trending/top/newest) hide in 'mine' scope — meaningless for a handful of own templates. Category filter + search still apply (client-side). - Takedown cards link to the source server's Publish tab instead of the detail route (which 410s); everything else opens the detail page. Backend: - GET /v1/templates/mine (requireAuth) — all own templates, any status, registered before /:slug so the static route always wins the match. - GET /v1/templates/:slug — now does an optional session check: the OWNER can view their own hidden/draft template (so a 'My templates' card click never dead-ends in a 404). takedown stays 410 for everyone, owner included — that's an admin decision, not the owner's to reverse. Detail page: - Fork CTA is gated on status === 'public'. For a non-public template the owner sees an amber 'not forkable — re-share from the Publish tab' notice plus a 'Manage in server' link, instead of a Fork button that would fail silently. Verified: - GET /v1/templates/mine → marco's 1 template; 401 without auth - Owner GET of a hidden template → 200 status:hidden; anon → 404 - Dashboard nav shows Marketplace (screenshot) - /templates 'My templates' toggle → only own template, public badge, sort tabs hidden (screenshot)
2026-05-20 17:18:58 +02:00
status: 'draft' | 'public' | 'hidden' | 'takedown';
feat(marketplace): template publish + fork + voting/ranking + admin moderation What this enables: - A user builds an MCP server. If others would benefit, they click 'Publish as template' on their server detail page. The spec + pre-rendered TypeScript snapshot is preserved. - Visitors browse /templates, filter by category, sort by trending/top/newest. Each template card shows fork count + active deployment count as natural manipulation-resistant popularity signal. - /templates/[slug] shows the full plan: tool list with input schemas, required-credential explanations (with 'how to get one' deep links), and a collapsible code preview so users can audit before forking. - Fork is one click → /servers/new?template=slug. The wizard skips Step 1 and pre-fills Step 2 with the template's parsed spec. Forker only fills in their own credentials. mcp_servers.template_id is recorded; template.fork_count is bumped atomically. Each fork gets its own isolated container with its own port, its own AES-256 secrets — the template author has zero visibility into the fork's traffic or data. - Admin /admin/templates moderation: verify quality templates (shows shield badge in marketplace), hide low-effort ones, takedown anything malicious. Takedowns cascade-pause every fork container — owners must re-deploy. Why template+fork instead of shared-container: - Shared containers would mean the publisher's quota + their secrets + their logs are exposed to forkers. Bad ergonomics, bad security, bad ownership. - Templates/forks decouple the spec (shared, vouched-for) from the runtime (isolated per user). Network-effect moat without the trust collapse. Why no 5-star voting in v1: - Manipulation-anfällig, empty lists without adoption. We use fork count + active deploys + verified badge. Trending algorithm: score = (activeDeploys * 3 + forks) / sqrt(ageDays + 1) Real signal, no brigading attack surface. Backend: - New schema: templates table (16 cols incl. tools_schema, generated_code, required_secrets, allowedDomains, status enum, verified, fork_count). - mcp_servers.template_id FK + idx for fork lookup. - @bmm/types: SpecEdit unchanged, CreateServerInput accepts optional templateId. - preview-cache.ts: new cachePrebuiltCode/loadPrebuiltCode for storing the template's full rendered server.ts alongside the spec. Generator worker detects this and skips the render step — uses the audited pre-built code verbatim. Banned-pattern re-scan at publish time. - routes/templates.ts: 5 public/auth routes + 2 admin routes. Banned-pattern re-scan before publish. Slug auto-uniqued. forkCount atomic-increment via SQL. UI: - /templates marketplace with trending/top/newest tabs, category filter, search. Cards show forks + live count + author + verified badge. - /templates/[slug] full detail with tools, credentials-with-hints, expandable code preview, fork CTA, ownership + stats sidebar, 'forking is safe' explainer. - /servers/new?template=slug — wizard auto-jumps to Step 2 with template spec pre-filled, fork banner at top with link back to template. - /servers/[id] new Publish tab with title, category, descriptions, per-secret hint fields (description + howToGetUrl per UPPER_SNAKE_CASE key). - /admin/templates moderation with verify/hide/takedown actions. - Marketing nav now includes /templates. Verified end-to-end: - Published Echo Demo Template from marco@test.local's live server - Marketplace lists it correctly with stats - Detail page renders with all sections - Fork CTA navigates to wizard with ?template= param - Wizard skips Step 1, shows fork banner, pre-fills spec - Build succeeds in ~10s (cached spec + prebuilt code path skips Claude AND render), container live on :4109 with proper OAuth 401 → token → 200 flow - DB: templates.fork_count=1, activeDeployments=1, mcp_servers.template_id populated on the fork - /admin/templates shows the new template with verify/hide/takedown controls
2026-05-19 23:22:35 +02:00
verified: boolean;
forkCount: number;
activeDeployments: number;
ownerName: string | null;
ownerOrgName: string | null;
feat(marketplace): dashboard nav link + My-templates filter The logged-in user can now reach the marketplace and filter to their own templates. Dashboard nav: - Added 'Marketplace' item (Overview · Servers · Marketplace · Audit · Settings). /templates page — login-aware: - Detects session via /v1/auth/me. Logged-in users get a 'Dashboard' + '+ New server' header instead of 'Home' + 'Start building'. - New [All templates | My templates] scope toggle, shown only when logged in. - 'My templates' loads GET /v1/templates/mine and shows EVERY status the user owns (public / hidden / draft / takedown) with a colored status badge on each card — so a template you unshared doesn't appear to have vanished. - Sort tabs (trending/top/newest) hide in 'mine' scope — meaningless for a handful of own templates. Category filter + search still apply (client-side). - Takedown cards link to the source server's Publish tab instead of the detail route (which 410s); everything else opens the detail page. Backend: - GET /v1/templates/mine (requireAuth) — all own templates, any status, registered before /:slug so the static route always wins the match. - GET /v1/templates/:slug — now does an optional session check: the OWNER can view their own hidden/draft template (so a 'My templates' card click never dead-ends in a 404). takedown stays 410 for everyone, owner included — that's an admin decision, not the owner's to reverse. Detail page: - Fork CTA is gated on status === 'public'. For a non-public template the owner sees an amber 'not forkable — re-share from the Publish tab' notice plus a 'Manage in server' link, instead of a Fork button that would fail silently. Verified: - GET /v1/templates/mine → marco's 1 template; 401 without auth - Owner GET of a hidden template → 200 status:hidden; anon → 404 - Dashboard nav shows Marketplace (screenshot) - /templates 'My templates' toggle → only own template, public badge, sort tabs hidden (screenshot)
2026-05-20 17:18:58 +02:00
sourceServerId: string | null;
feat(marketplace): template publish + fork + voting/ranking + admin moderation What this enables: - A user builds an MCP server. If others would benefit, they click 'Publish as template' on their server detail page. The spec + pre-rendered TypeScript snapshot is preserved. - Visitors browse /templates, filter by category, sort by trending/top/newest. Each template card shows fork count + active deployment count as natural manipulation-resistant popularity signal. - /templates/[slug] shows the full plan: tool list with input schemas, required-credential explanations (with 'how to get one' deep links), and a collapsible code preview so users can audit before forking. - Fork is one click → /servers/new?template=slug. The wizard skips Step 1 and pre-fills Step 2 with the template's parsed spec. Forker only fills in their own credentials. mcp_servers.template_id is recorded; template.fork_count is bumped atomically. Each fork gets its own isolated container with its own port, its own AES-256 secrets — the template author has zero visibility into the fork's traffic or data. - Admin /admin/templates moderation: verify quality templates (shows shield badge in marketplace), hide low-effort ones, takedown anything malicious. Takedowns cascade-pause every fork container — owners must re-deploy. Why template+fork instead of shared-container: - Shared containers would mean the publisher's quota + their secrets + their logs are exposed to forkers. Bad ergonomics, bad security, bad ownership. - Templates/forks decouple the spec (shared, vouched-for) from the runtime (isolated per user). Network-effect moat without the trust collapse. Why no 5-star voting in v1: - Manipulation-anfällig, empty lists without adoption. We use fork count + active deploys + verified badge. Trending algorithm: score = (activeDeploys * 3 + forks) / sqrt(ageDays + 1) Real signal, no brigading attack surface. Backend: - New schema: templates table (16 cols incl. tools_schema, generated_code, required_secrets, allowedDomains, status enum, verified, fork_count). - mcp_servers.template_id FK + idx for fork lookup. - @bmm/types: SpecEdit unchanged, CreateServerInput accepts optional templateId. - preview-cache.ts: new cachePrebuiltCode/loadPrebuiltCode for storing the template's full rendered server.ts alongside the spec. Generator worker detects this and skips the render step — uses the audited pre-built code verbatim. Banned-pattern re-scan at publish time. - routes/templates.ts: 5 public/auth routes + 2 admin routes. Banned-pattern re-scan before publish. Slug auto-uniqued. forkCount atomic-increment via SQL. UI: - /templates marketplace with trending/top/newest tabs, category filter, search. Cards show forks + live count + author + verified badge. - /templates/[slug] full detail with tools, credentials-with-hints, expandable code preview, fork CTA, ownership + stats sidebar, 'forking is safe' explainer. - /servers/new?template=slug — wizard auto-jumps to Step 2 with template spec pre-filled, fork banner at top with link back to template. - /servers/[id] new Publish tab with title, category, descriptions, per-secret hint fields (description + howToGetUrl per UPPER_SNAKE_CASE key). - /admin/templates moderation with verify/hide/takedown actions. - Marketing nav now includes /templates. Verified end-to-end: - Published Echo Demo Template from marco@test.local's live server - Marketplace lists it correctly with stats - Detail page renders with all sections - Fork CTA navigates to wizard with ?template= param - Wizard skips Step 1, shows fork banner, pre-fills spec - Build succeeds in ~10s (cached spec + prebuilt code path skips Claude AND render), container live on :4109 with proper OAuth 401 → token → 200 flow - DB: templates.fork_count=1, activeDeployments=1, mcp_servers.template_id populated on the fork - /admin/templates shows the new template with verify/hide/takedown controls
2026-05-19 23:22:35 +02:00
createdAt: string;
}
type Sort = 'trending' | 'top' | 'newest';
feat(marketplace): dashboard nav link + My-templates filter The logged-in user can now reach the marketplace and filter to their own templates. Dashboard nav: - Added 'Marketplace' item (Overview · Servers · Marketplace · Audit · Settings). /templates page — login-aware: - Detects session via /v1/auth/me. Logged-in users get a 'Dashboard' + '+ New server' header instead of 'Home' + 'Start building'. - New [All templates | My templates] scope toggle, shown only when logged in. - 'My templates' loads GET /v1/templates/mine and shows EVERY status the user owns (public / hidden / draft / takedown) with a colored status badge on each card — so a template you unshared doesn't appear to have vanished. - Sort tabs (trending/top/newest) hide in 'mine' scope — meaningless for a handful of own templates. Category filter + search still apply (client-side). - Takedown cards link to the source server's Publish tab instead of the detail route (which 410s); everything else opens the detail page. Backend: - GET /v1/templates/mine (requireAuth) — all own templates, any status, registered before /:slug so the static route always wins the match. - GET /v1/templates/:slug — now does an optional session check: the OWNER can view their own hidden/draft template (so a 'My templates' card click never dead-ends in a 404). takedown stays 410 for everyone, owner included — that's an admin decision, not the owner's to reverse. Detail page: - Fork CTA is gated on status === 'public'. For a non-public template the owner sees an amber 'not forkable — re-share from the Publish tab' notice plus a 'Manage in server' link, instead of a Fork button that would fail silently. Verified: - GET /v1/templates/mine → marco's 1 template; 401 without auth - Owner GET of a hidden template → 200 status:hidden; anon → 404 - Dashboard nav shows Marketplace (screenshot) - /templates 'My templates' toggle → only own template, public badge, sort tabs hidden (screenshot)
2026-05-20 17:18:58 +02:00
type Scope = 'all' | 'mine';
const STATUS_STYLE: Record<Template['status'], string> = {
public: 'border-emerald-400/40 bg-emerald-400/10 text-emerald-300',
hidden: 'border-amber-400/40 bg-amber-400/10 text-amber-300',
takedown: 'border-red-400/40 bg-red-400/10 text-red-300',
draft: 'border-zinc-400/40 bg-zinc-400/10 text-zinc-300',
};
feat(marketplace): template publish + fork + voting/ranking + admin moderation What this enables: - A user builds an MCP server. If others would benefit, they click 'Publish as template' on their server detail page. The spec + pre-rendered TypeScript snapshot is preserved. - Visitors browse /templates, filter by category, sort by trending/top/newest. Each template card shows fork count + active deployment count as natural manipulation-resistant popularity signal. - /templates/[slug] shows the full plan: tool list with input schemas, required-credential explanations (with 'how to get one' deep links), and a collapsible code preview so users can audit before forking. - Fork is one click → /servers/new?template=slug. The wizard skips Step 1 and pre-fills Step 2 with the template's parsed spec. Forker only fills in their own credentials. mcp_servers.template_id is recorded; template.fork_count is bumped atomically. Each fork gets its own isolated container with its own port, its own AES-256 secrets — the template author has zero visibility into the fork's traffic or data. - Admin /admin/templates moderation: verify quality templates (shows shield badge in marketplace), hide low-effort ones, takedown anything malicious. Takedowns cascade-pause every fork container — owners must re-deploy. Why template+fork instead of shared-container: - Shared containers would mean the publisher's quota + their secrets + their logs are exposed to forkers. Bad ergonomics, bad security, bad ownership. - Templates/forks decouple the spec (shared, vouched-for) from the runtime (isolated per user). Network-effect moat without the trust collapse. Why no 5-star voting in v1: - Manipulation-anfällig, empty lists without adoption. We use fork count + active deploys + verified badge. Trending algorithm: score = (activeDeploys * 3 + forks) / sqrt(ageDays + 1) Real signal, no brigading attack surface. Backend: - New schema: templates table (16 cols incl. tools_schema, generated_code, required_secrets, allowedDomains, status enum, verified, fork_count). - mcp_servers.template_id FK + idx for fork lookup. - @bmm/types: SpecEdit unchanged, CreateServerInput accepts optional templateId. - preview-cache.ts: new cachePrebuiltCode/loadPrebuiltCode for storing the template's full rendered server.ts alongside the spec. Generator worker detects this and skips the render step — uses the audited pre-built code verbatim. Banned-pattern re-scan at publish time. - routes/templates.ts: 5 public/auth routes + 2 admin routes. Banned-pattern re-scan before publish. Slug auto-uniqued. forkCount atomic-increment via SQL. UI: - /templates marketplace with trending/top/newest tabs, category filter, search. Cards show forks + live count + author + verified badge. - /templates/[slug] full detail with tools, credentials-with-hints, expandable code preview, fork CTA, ownership + stats sidebar, 'forking is safe' explainer. - /servers/new?template=slug — wizard auto-jumps to Step 2 with template spec pre-filled, fork banner at top with link back to template. - /servers/[id] new Publish tab with title, category, descriptions, per-secret hint fields (description + howToGetUrl per UPPER_SNAKE_CASE key). - /admin/templates moderation with verify/hide/takedown actions. - Marketing nav now includes /templates. Verified end-to-end: - Published Echo Demo Template from marco@test.local's live server - Marketplace lists it correctly with stats - Detail page renders with all sections - Fork CTA navigates to wizard with ?template= param - Wizard skips Step 1, shows fork banner, pre-fills spec - Build succeeds in ~10s (cached spec + prebuilt code path skips Claude AND render), container live on :4109 with proper OAuth 401 → token → 200 flow - DB: templates.fork_count=1, activeDeployments=1, mcp_servers.template_id populated on the fork - /admin/templates shows the new template with verify/hide/takedown controls
2026-05-19 23:22:35 +02:00
export default function TemplatesMarketplace() {
feat(marketplace): dashboard nav link + My-templates filter The logged-in user can now reach the marketplace and filter to their own templates. Dashboard nav: - Added 'Marketplace' item (Overview · Servers · Marketplace · Audit · Settings). /templates page — login-aware: - Detects session via /v1/auth/me. Logged-in users get a 'Dashboard' + '+ New server' header instead of 'Home' + 'Start building'. - New [All templates | My templates] scope toggle, shown only when logged in. - 'My templates' loads GET /v1/templates/mine and shows EVERY status the user owns (public / hidden / draft / takedown) with a colored status badge on each card — so a template you unshared doesn't appear to have vanished. - Sort tabs (trending/top/newest) hide in 'mine' scope — meaningless for a handful of own templates. Category filter + search still apply (client-side). - Takedown cards link to the source server's Publish tab instead of the detail route (which 410s); everything else opens the detail page. Backend: - GET /v1/templates/mine (requireAuth) — all own templates, any status, registered before /:slug so the static route always wins the match. - GET /v1/templates/:slug — now does an optional session check: the OWNER can view their own hidden/draft template (so a 'My templates' card click never dead-ends in a 404). takedown stays 410 for everyone, owner included — that's an admin decision, not the owner's to reverse. Detail page: - Fork CTA is gated on status === 'public'. For a non-public template the owner sees an amber 'not forkable — re-share from the Publish tab' notice plus a 'Manage in server' link, instead of a Fork button that would fail silently. Verified: - GET /v1/templates/mine → marco's 1 template; 401 without auth - Owner GET of a hidden template → 200 status:hidden; anon → 404 - Dashboard nav shows Marketplace (screenshot) - /templates 'My templates' toggle → only own template, public badge, sort tabs hidden (screenshot)
2026-05-20 17:18:58 +02:00
const [me, setMe] = useState<{ email: string } | null | undefined>(undefined);
const [scope, setScope] = useState<Scope>('all');
feat(marketplace): template publish + fork + voting/ranking + admin moderation What this enables: - A user builds an MCP server. If others would benefit, they click 'Publish as template' on their server detail page. The spec + pre-rendered TypeScript snapshot is preserved. - Visitors browse /templates, filter by category, sort by trending/top/newest. Each template card shows fork count + active deployment count as natural manipulation-resistant popularity signal. - /templates/[slug] shows the full plan: tool list with input schemas, required-credential explanations (with 'how to get one' deep links), and a collapsible code preview so users can audit before forking. - Fork is one click → /servers/new?template=slug. The wizard skips Step 1 and pre-fills Step 2 with the template's parsed spec. Forker only fills in their own credentials. mcp_servers.template_id is recorded; template.fork_count is bumped atomically. Each fork gets its own isolated container with its own port, its own AES-256 secrets — the template author has zero visibility into the fork's traffic or data. - Admin /admin/templates moderation: verify quality templates (shows shield badge in marketplace), hide low-effort ones, takedown anything malicious. Takedowns cascade-pause every fork container — owners must re-deploy. Why template+fork instead of shared-container: - Shared containers would mean the publisher's quota + their secrets + their logs are exposed to forkers. Bad ergonomics, bad security, bad ownership. - Templates/forks decouple the spec (shared, vouched-for) from the runtime (isolated per user). Network-effect moat without the trust collapse. Why no 5-star voting in v1: - Manipulation-anfällig, empty lists without adoption. We use fork count + active deploys + verified badge. Trending algorithm: score = (activeDeploys * 3 + forks) / sqrt(ageDays + 1) Real signal, no brigading attack surface. Backend: - New schema: templates table (16 cols incl. tools_schema, generated_code, required_secrets, allowedDomains, status enum, verified, fork_count). - mcp_servers.template_id FK + idx for fork lookup. - @bmm/types: SpecEdit unchanged, CreateServerInput accepts optional templateId. - preview-cache.ts: new cachePrebuiltCode/loadPrebuiltCode for storing the template's full rendered server.ts alongside the spec. Generator worker detects this and skips the render step — uses the audited pre-built code verbatim. Banned-pattern re-scan at publish time. - routes/templates.ts: 5 public/auth routes + 2 admin routes. Banned-pattern re-scan before publish. Slug auto-uniqued. forkCount atomic-increment via SQL. UI: - /templates marketplace with trending/top/newest tabs, category filter, search. Cards show forks + live count + author + verified badge. - /templates/[slug] full detail with tools, credentials-with-hints, expandable code preview, fork CTA, ownership + stats sidebar, 'forking is safe' explainer. - /servers/new?template=slug — wizard auto-jumps to Step 2 with template spec pre-filled, fork banner at top with link back to template. - /servers/[id] new Publish tab with title, category, descriptions, per-secret hint fields (description + howToGetUrl per UPPER_SNAKE_CASE key). - /admin/templates moderation with verify/hide/takedown actions. - Marketing nav now includes /templates. Verified end-to-end: - Published Echo Demo Template from marco@test.local's live server - Marketplace lists it correctly with stats - Detail page renders with all sections - Fork CTA navigates to wizard with ?template= param - Wizard skips Step 1, shows fork banner, pre-fills spec - Build succeeds in ~10s (cached spec + prebuilt code path skips Claude AND render), container live on :4109 with proper OAuth 401 → token → 200 flow - DB: templates.fork_count=1, activeDeployments=1, mcp_servers.template_id populated on the fork - /admin/templates shows the new template with verify/hide/takedown controls
2026-05-19 23:22:35 +02:00
const [templates, setTemplates] = useState<Template[] | null>(null);
const [categories, setCategories] = useState<string[]>([]);
const [sort, setSort] = useState<Sort>('trending');
const [category, setCategory] = useState('');
const [search, setSearch] = useState('');
feat(marketplace): dashboard nav link + My-templates filter The logged-in user can now reach the marketplace and filter to their own templates. Dashboard nav: - Added 'Marketplace' item (Overview · Servers · Marketplace · Audit · Settings). /templates page — login-aware: - Detects session via /v1/auth/me. Logged-in users get a 'Dashboard' + '+ New server' header instead of 'Home' + 'Start building'. - New [All templates | My templates] scope toggle, shown only when logged in. - 'My templates' loads GET /v1/templates/mine and shows EVERY status the user owns (public / hidden / draft / takedown) with a colored status badge on each card — so a template you unshared doesn't appear to have vanished. - Sort tabs (trending/top/newest) hide in 'mine' scope — meaningless for a handful of own templates. Category filter + search still apply (client-side). - Takedown cards link to the source server's Publish tab instead of the detail route (which 410s); everything else opens the detail page. Backend: - GET /v1/templates/mine (requireAuth) — all own templates, any status, registered before /:slug so the static route always wins the match. - GET /v1/templates/:slug — now does an optional session check: the OWNER can view their own hidden/draft template (so a 'My templates' card click never dead-ends in a 404). takedown stays 410 for everyone, owner included — that's an admin decision, not the owner's to reverse. Detail page: - Fork CTA is gated on status === 'public'. For a non-public template the owner sees an amber 'not forkable — re-share from the Publish tab' notice plus a 'Manage in server' link, instead of a Fork button that would fail silently. Verified: - GET /v1/templates/mine → marco's 1 template; 401 without auth - Owner GET of a hidden template → 200 status:hidden; anon → 404 - Dashboard nav shows Marketplace (screenshot) - /templates 'My templates' toggle → only own template, public badge, sort tabs hidden (screenshot)
2026-05-20 17:18:58 +02:00
// Detect login state once
feat(marketplace): template publish + fork + voting/ranking + admin moderation What this enables: - A user builds an MCP server. If others would benefit, they click 'Publish as template' on their server detail page. The spec + pre-rendered TypeScript snapshot is preserved. - Visitors browse /templates, filter by category, sort by trending/top/newest. Each template card shows fork count + active deployment count as natural manipulation-resistant popularity signal. - /templates/[slug] shows the full plan: tool list with input schemas, required-credential explanations (with 'how to get one' deep links), and a collapsible code preview so users can audit before forking. - Fork is one click → /servers/new?template=slug. The wizard skips Step 1 and pre-fills Step 2 with the template's parsed spec. Forker only fills in their own credentials. mcp_servers.template_id is recorded; template.fork_count is bumped atomically. Each fork gets its own isolated container with its own port, its own AES-256 secrets — the template author has zero visibility into the fork's traffic or data. - Admin /admin/templates moderation: verify quality templates (shows shield badge in marketplace), hide low-effort ones, takedown anything malicious. Takedowns cascade-pause every fork container — owners must re-deploy. Why template+fork instead of shared-container: - Shared containers would mean the publisher's quota + their secrets + their logs are exposed to forkers. Bad ergonomics, bad security, bad ownership. - Templates/forks decouple the spec (shared, vouched-for) from the runtime (isolated per user). Network-effect moat without the trust collapse. Why no 5-star voting in v1: - Manipulation-anfällig, empty lists without adoption. We use fork count + active deploys + verified badge. Trending algorithm: score = (activeDeploys * 3 + forks) / sqrt(ageDays + 1) Real signal, no brigading attack surface. Backend: - New schema: templates table (16 cols incl. tools_schema, generated_code, required_secrets, allowedDomains, status enum, verified, fork_count). - mcp_servers.template_id FK + idx for fork lookup. - @bmm/types: SpecEdit unchanged, CreateServerInput accepts optional templateId. - preview-cache.ts: new cachePrebuiltCode/loadPrebuiltCode for storing the template's full rendered server.ts alongside the spec. Generator worker detects this and skips the render step — uses the audited pre-built code verbatim. Banned-pattern re-scan at publish time. - routes/templates.ts: 5 public/auth routes + 2 admin routes. Banned-pattern re-scan before publish. Slug auto-uniqued. forkCount atomic-increment via SQL. UI: - /templates marketplace with trending/top/newest tabs, category filter, search. Cards show forks + live count + author + verified badge. - /templates/[slug] full detail with tools, credentials-with-hints, expandable code preview, fork CTA, ownership + stats sidebar, 'forking is safe' explainer. - /servers/new?template=slug — wizard auto-jumps to Step 2 with template spec pre-filled, fork banner at top with link back to template. - /servers/[id] new Publish tab with title, category, descriptions, per-secret hint fields (description + howToGetUrl per UPPER_SNAKE_CASE key). - /admin/templates moderation with verify/hide/takedown actions. - Marketing nav now includes /templates. Verified end-to-end: - Published Echo Demo Template from marco@test.local's live server - Marketplace lists it correctly with stats - Detail page renders with all sections - Fork CTA navigates to wizard with ?template= param - Wizard skips Step 1, shows fork banner, pre-fills spec - Build succeeds in ~10s (cached spec + prebuilt code path skips Claude AND render), container live on :4109 with proper OAuth 401 → token → 200 flow - DB: templates.fork_count=1, activeDeployments=1, mcp_servers.template_id populated on the fork - /admin/templates shows the new template with verify/hide/takedown controls
2026-05-19 23:22:35 +02:00
useEffect(() => {
feat(marketplace): dashboard nav link + My-templates filter The logged-in user can now reach the marketplace and filter to their own templates. Dashboard nav: - Added 'Marketplace' item (Overview · Servers · Marketplace · Audit · Settings). /templates page — login-aware: - Detects session via /v1/auth/me. Logged-in users get a 'Dashboard' + '+ New server' header instead of 'Home' + 'Start building'. - New [All templates | My templates] scope toggle, shown only when logged in. - 'My templates' loads GET /v1/templates/mine and shows EVERY status the user owns (public / hidden / draft / takedown) with a colored status badge on each card — so a template you unshared doesn't appear to have vanished. - Sort tabs (trending/top/newest) hide in 'mine' scope — meaningless for a handful of own templates. Category filter + search still apply (client-side). - Takedown cards link to the source server's Publish tab instead of the detail route (which 410s); everything else opens the detail page. Backend: - GET /v1/templates/mine (requireAuth) — all own templates, any status, registered before /:slug so the static route always wins the match. - GET /v1/templates/:slug — now does an optional session check: the OWNER can view their own hidden/draft template (so a 'My templates' card click never dead-ends in a 404). takedown stays 410 for everyone, owner included — that's an admin decision, not the owner's to reverse. Detail page: - Fork CTA is gated on status === 'public'. For a non-public template the owner sees an amber 'not forkable — re-share from the Publish tab' notice plus a 'Manage in server' link, instead of a Fork button that would fail silently. Verified: - GET /v1/templates/mine → marco's 1 template; 401 without auth - Owner GET of a hidden template → 200 status:hidden; anon → 404 - Dashboard nav shows Marketplace (screenshot) - /templates 'My templates' toggle → only own template, public badge, sort tabs hidden (screenshot)
2026-05-20 17:18:58 +02:00
apiFetch<{ user: { email: string } }>('/v1/auth/me')
.then((r) => setMe({ email: r.user.email }))
.catch(() => setMe(null));
}, []);
// Load templates whenever scope/sort/category changes
useEffect(() => {
setTemplates(null);
if (scope === 'mine') {
apiFetch<{ templates: Template[]; categories: string[] }>('/v1/templates/mine')
.then((r) => {
setTemplates(r.templates);
setCategories(r.categories);
})
.catch(() => setTemplates([]));
} else {
const params = new URLSearchParams({ sort });
if (category) params.set('category', category);
apiFetch<{ templates: Template[]; categories: string[] }>(`/v1/templates?${params}`)
.then((r) => {
setTemplates(r.templates);
setCategories(r.categories);
})
.catch(() => setTemplates([]));
}
}, [scope, sort, category]);
const visible = templates?.filter((t) => {
if (search) {
const q = search.toLowerCase();
if (!t.title.toLowerCase().includes(q) && !t.shortDescription.toLowerCase().includes(q)) {
return false;
}
}
// category filter is server-side for 'all', client-side for 'mine'
if (scope === 'mine' && category && t.category !== category) return false;
return true;
});
const loggedIn = me != null;
feat(marketplace): template publish + fork + voting/ranking + admin moderation What this enables: - A user builds an MCP server. If others would benefit, they click 'Publish as template' on their server detail page. The spec + pre-rendered TypeScript snapshot is preserved. - Visitors browse /templates, filter by category, sort by trending/top/newest. Each template card shows fork count + active deployment count as natural manipulation-resistant popularity signal. - /templates/[slug] shows the full plan: tool list with input schemas, required-credential explanations (with 'how to get one' deep links), and a collapsible code preview so users can audit before forking. - Fork is one click → /servers/new?template=slug. The wizard skips Step 1 and pre-fills Step 2 with the template's parsed spec. Forker only fills in their own credentials. mcp_servers.template_id is recorded; template.fork_count is bumped atomically. Each fork gets its own isolated container with its own port, its own AES-256 secrets — the template author has zero visibility into the fork's traffic or data. - Admin /admin/templates moderation: verify quality templates (shows shield badge in marketplace), hide low-effort ones, takedown anything malicious. Takedowns cascade-pause every fork container — owners must re-deploy. Why template+fork instead of shared-container: - Shared containers would mean the publisher's quota + their secrets + their logs are exposed to forkers. Bad ergonomics, bad security, bad ownership. - Templates/forks decouple the spec (shared, vouched-for) from the runtime (isolated per user). Network-effect moat without the trust collapse. Why no 5-star voting in v1: - Manipulation-anfällig, empty lists without adoption. We use fork count + active deploys + verified badge. Trending algorithm: score = (activeDeploys * 3 + forks) / sqrt(ageDays + 1) Real signal, no brigading attack surface. Backend: - New schema: templates table (16 cols incl. tools_schema, generated_code, required_secrets, allowedDomains, status enum, verified, fork_count). - mcp_servers.template_id FK + idx for fork lookup. - @bmm/types: SpecEdit unchanged, CreateServerInput accepts optional templateId. - preview-cache.ts: new cachePrebuiltCode/loadPrebuiltCode for storing the template's full rendered server.ts alongside the spec. Generator worker detects this and skips the render step — uses the audited pre-built code verbatim. Banned-pattern re-scan at publish time. - routes/templates.ts: 5 public/auth routes + 2 admin routes. Banned-pattern re-scan before publish. Slug auto-uniqued. forkCount atomic-increment via SQL. UI: - /templates marketplace with trending/top/newest tabs, category filter, search. Cards show forks + live count + author + verified badge. - /templates/[slug] full detail with tools, credentials-with-hints, expandable code preview, fork CTA, ownership + stats sidebar, 'forking is safe' explainer. - /servers/new?template=slug — wizard auto-jumps to Step 2 with template spec pre-filled, fork banner at top with link back to template. - /servers/[id] new Publish tab with title, category, descriptions, per-secret hint fields (description + howToGetUrl per UPPER_SNAKE_CASE key). - /admin/templates moderation with verify/hide/takedown actions. - Marketing nav now includes /templates. Verified end-to-end: - Published Echo Demo Template from marco@test.local's live server - Marketplace lists it correctly with stats - Detail page renders with all sections - Fork CTA navigates to wizard with ?template= param - Wizard skips Step 1, shows fork banner, pre-fills spec - Build succeeds in ~10s (cached spec + prebuilt code path skips Claude AND render), container live on :4109 with proper OAuth 401 → token → 200 flow - DB: templates.fork_count=1, activeDeployments=1, mcp_servers.template_id populated on the fork - /admin/templates shows the new template with verify/hide/takedown controls
2026-05-19 23:22:35 +02:00
return (
<div className="flex min-h-screen flex-col">
<header className="sticky top-0 z-50 border-b border-[--color-border] bg-[--color-bg]/85 backdrop-blur-md">
<div className="mx-auto flex h-12 max-w-6xl items-center justify-between px-6">
<div className="flex items-center gap-3">
<Logo />
<span className="text-[12.5px] text-[--color-fg-subtle]">/ templates</span>
</div>
<nav className="flex items-center gap-2">
feat(marketplace): dashboard nav link + My-templates filter The logged-in user can now reach the marketplace and filter to their own templates. Dashboard nav: - Added 'Marketplace' item (Overview · Servers · Marketplace · Audit · Settings). /templates page — login-aware: - Detects session via /v1/auth/me. Logged-in users get a 'Dashboard' + '+ New server' header instead of 'Home' + 'Start building'. - New [All templates | My templates] scope toggle, shown only when logged in. - 'My templates' loads GET /v1/templates/mine and shows EVERY status the user owns (public / hidden / draft / takedown) with a colored status badge on each card — so a template you unshared doesn't appear to have vanished. - Sort tabs (trending/top/newest) hide in 'mine' scope — meaningless for a handful of own templates. Category filter + search still apply (client-side). - Takedown cards link to the source server's Publish tab instead of the detail route (which 410s); everything else opens the detail page. Backend: - GET /v1/templates/mine (requireAuth) — all own templates, any status, registered before /:slug so the static route always wins the match. - GET /v1/templates/:slug — now does an optional session check: the OWNER can view their own hidden/draft template (so a 'My templates' card click never dead-ends in a 404). takedown stays 410 for everyone, owner included — that's an admin decision, not the owner's to reverse. Detail page: - Fork CTA is gated on status === 'public'. For a non-public template the owner sees an amber 'not forkable — re-share from the Publish tab' notice plus a 'Manage in server' link, instead of a Fork button that would fail silently. Verified: - GET /v1/templates/mine → marco's 1 template; 401 without auth - Owner GET of a hidden template → 200 status:hidden; anon → 404 - Dashboard nav shows Marketplace (screenshot) - /templates 'My templates' toggle → only own template, public badge, sort tabs hidden (screenshot)
2026-05-20 17:18:58 +02:00
{loggedIn ? (
<>
<Link
href="/dashboard"
className="text-[12.5px] text-[--color-fg-muted] transition-colors hover:text-[--color-fg]"
>
Dashboard
</Link>
<Link
href="/servers/new"
className="rounded-md bg-[--color-accent] px-3 py-1.5 text-[12.5px] font-medium text-white transition-colors duration-200 hover:bg-[#5557e8]"
>
+ New server
</Link>
</>
) : (
<>
<Link
href="/"
className="text-[12.5px] text-[--color-fg-muted] transition-colors hover:text-[--color-fg]"
>
Home
</Link>
<Link
href="/login"
className="rounded-md bg-[--color-accent] px-3 py-1.5 text-[12.5px] font-medium text-white transition-colors duration-200 hover:bg-[#5557e8]"
>
Start building
</Link>
</>
)}
feat(marketplace): template publish + fork + voting/ranking + admin moderation What this enables: - A user builds an MCP server. If others would benefit, they click 'Publish as template' on their server detail page. The spec + pre-rendered TypeScript snapshot is preserved. - Visitors browse /templates, filter by category, sort by trending/top/newest. Each template card shows fork count + active deployment count as natural manipulation-resistant popularity signal. - /templates/[slug] shows the full plan: tool list with input schemas, required-credential explanations (with 'how to get one' deep links), and a collapsible code preview so users can audit before forking. - Fork is one click → /servers/new?template=slug. The wizard skips Step 1 and pre-fills Step 2 with the template's parsed spec. Forker only fills in their own credentials. mcp_servers.template_id is recorded; template.fork_count is bumped atomically. Each fork gets its own isolated container with its own port, its own AES-256 secrets — the template author has zero visibility into the fork's traffic or data. - Admin /admin/templates moderation: verify quality templates (shows shield badge in marketplace), hide low-effort ones, takedown anything malicious. Takedowns cascade-pause every fork container — owners must re-deploy. Why template+fork instead of shared-container: - Shared containers would mean the publisher's quota + their secrets + their logs are exposed to forkers. Bad ergonomics, bad security, bad ownership. - Templates/forks decouple the spec (shared, vouched-for) from the runtime (isolated per user). Network-effect moat without the trust collapse. Why no 5-star voting in v1: - Manipulation-anfällig, empty lists without adoption. We use fork count + active deploys + verified badge. Trending algorithm: score = (activeDeploys * 3 + forks) / sqrt(ageDays + 1) Real signal, no brigading attack surface. Backend: - New schema: templates table (16 cols incl. tools_schema, generated_code, required_secrets, allowedDomains, status enum, verified, fork_count). - mcp_servers.template_id FK + idx for fork lookup. - @bmm/types: SpecEdit unchanged, CreateServerInput accepts optional templateId. - preview-cache.ts: new cachePrebuiltCode/loadPrebuiltCode for storing the template's full rendered server.ts alongside the spec. Generator worker detects this and skips the render step — uses the audited pre-built code verbatim. Banned-pattern re-scan at publish time. - routes/templates.ts: 5 public/auth routes + 2 admin routes. Banned-pattern re-scan before publish. Slug auto-uniqued. forkCount atomic-increment via SQL. UI: - /templates marketplace with trending/top/newest tabs, category filter, search. Cards show forks + live count + author + verified badge. - /templates/[slug] full detail with tools, credentials-with-hints, expandable code preview, fork CTA, ownership + stats sidebar, 'forking is safe' explainer. - /servers/new?template=slug — wizard auto-jumps to Step 2 with template spec pre-filled, fork banner at top with link back to template. - /servers/[id] new Publish tab with title, category, descriptions, per-secret hint fields (description + howToGetUrl per UPPER_SNAKE_CASE key). - /admin/templates moderation with verify/hide/takedown actions. - Marketing nav now includes /templates. Verified end-to-end: - Published Echo Demo Template from marco@test.local's live server - Marketplace lists it correctly with stats - Detail page renders with all sections - Fork CTA navigates to wizard with ?template= param - Wizard skips Step 1, shows fork banner, pre-fills spec - Build succeeds in ~10s (cached spec + prebuilt code path skips Claude AND render), container live on :4109 with proper OAuth 401 → token → 200 flow - DB: templates.fork_count=1, activeDeployments=1, mcp_servers.template_id populated on the fork - /admin/templates shows the new template with verify/hide/takedown controls
2026-05-19 23:22:35 +02:00
</nav>
</div>
</header>
<main className="mx-auto w-full max-w-6xl flex-1 px-6 py-12">
<header className="mb-8 max-w-2xl">
<div className="text-[11px] uppercase tracking-[0.16em] text-[--color-fg-subtle]">
Marketplace
</div>
<h1 className="mt-2 text-[32px] font-semibold tracking-tight">
MCP server templates
</h1>
<p className="mt-3 text-[14px] leading-relaxed text-[--color-fg-muted]">
Pre-built MCP servers from the community. Fork in one click your own container, your
own credentials, fully isolated. The template author never sees your data.
</p>
</header>
<div className="mb-6 flex flex-wrap items-center gap-3 border-b border-[--color-border] pb-4">
feat(marketplace): dashboard nav link + My-templates filter The logged-in user can now reach the marketplace and filter to their own templates. Dashboard nav: - Added 'Marketplace' item (Overview · Servers · Marketplace · Audit · Settings). /templates page — login-aware: - Detects session via /v1/auth/me. Logged-in users get a 'Dashboard' + '+ New server' header instead of 'Home' + 'Start building'. - New [All templates | My templates] scope toggle, shown only when logged in. - 'My templates' loads GET /v1/templates/mine and shows EVERY status the user owns (public / hidden / draft / takedown) with a colored status badge on each card — so a template you unshared doesn't appear to have vanished. - Sort tabs (trending/top/newest) hide in 'mine' scope — meaningless for a handful of own templates. Category filter + search still apply (client-side). - Takedown cards link to the source server's Publish tab instead of the detail route (which 410s); everything else opens the detail page. Backend: - GET /v1/templates/mine (requireAuth) — all own templates, any status, registered before /:slug so the static route always wins the match. - GET /v1/templates/:slug — now does an optional session check: the OWNER can view their own hidden/draft template (so a 'My templates' card click never dead-ends in a 404). takedown stays 410 for everyone, owner included — that's an admin decision, not the owner's to reverse. Detail page: - Fork CTA is gated on status === 'public'. For a non-public template the owner sees an amber 'not forkable — re-share from the Publish tab' notice plus a 'Manage in server' link, instead of a Fork button that would fail silently. Verified: - GET /v1/templates/mine → marco's 1 template; 401 without auth - Owner GET of a hidden template → 200 status:hidden; anon → 404 - Dashboard nav shows Marketplace (screenshot) - /templates 'My templates' toggle → only own template, public badge, sort tabs hidden (screenshot)
2026-05-20 17:18:58 +02:00
{loggedIn && (
<>
<div className="flex gap-1 rounded-md border border-[--color-border] bg-[--color-bg-subtle] p-0.5">
{(['all', 'mine'] as Scope[]).map((s) => (
<button
key={s}
type="button"
onClick={() => setScope(s)}
className={cn(
'rounded-[4px] px-2.5 py-1 text-[12.5px] capitalize transition-colors',
s === scope
? 'bg-[--color-bg-elevated] text-[--color-fg]'
: 'text-[--color-fg-muted] hover:text-[--color-fg]',
)}
>
{s === 'all' ? 'All templates' : 'My templates'}
</button>
))}
</div>
<div className="h-4 w-px bg-[--color-border]" />
</>
)}
{scope === 'all' && (
<>
<div className="flex gap-1">
{(['trending', 'top', 'newest'] as Sort[]).map((s) => (
<button
key={s}
type="button"
onClick={() => setSort(s)}
className={cn(
'rounded-md px-2.5 py-1 text-[12.5px] capitalize transition-colors',
s === sort
? 'bg-[--color-bg-elevated] text-[--color-fg]'
: 'text-[--color-fg-muted] hover:text-[--color-fg]',
)}
>
{s}
</button>
))}
</div>
<div className="h-4 w-px bg-[--color-border]" />
</>
)}
feat(marketplace): template publish + fork + voting/ranking + admin moderation What this enables: - A user builds an MCP server. If others would benefit, they click 'Publish as template' on their server detail page. The spec + pre-rendered TypeScript snapshot is preserved. - Visitors browse /templates, filter by category, sort by trending/top/newest. Each template card shows fork count + active deployment count as natural manipulation-resistant popularity signal. - /templates/[slug] shows the full plan: tool list with input schemas, required-credential explanations (with 'how to get one' deep links), and a collapsible code preview so users can audit before forking. - Fork is one click → /servers/new?template=slug. The wizard skips Step 1 and pre-fills Step 2 with the template's parsed spec. Forker only fills in their own credentials. mcp_servers.template_id is recorded; template.fork_count is bumped atomically. Each fork gets its own isolated container with its own port, its own AES-256 secrets — the template author has zero visibility into the fork's traffic or data. - Admin /admin/templates moderation: verify quality templates (shows shield badge in marketplace), hide low-effort ones, takedown anything malicious. Takedowns cascade-pause every fork container — owners must re-deploy. Why template+fork instead of shared-container: - Shared containers would mean the publisher's quota + their secrets + their logs are exposed to forkers. Bad ergonomics, bad security, bad ownership. - Templates/forks decouple the spec (shared, vouched-for) from the runtime (isolated per user). Network-effect moat without the trust collapse. Why no 5-star voting in v1: - Manipulation-anfällig, empty lists without adoption. We use fork count + active deploys + verified badge. Trending algorithm: score = (activeDeploys * 3 + forks) / sqrt(ageDays + 1) Real signal, no brigading attack surface. Backend: - New schema: templates table (16 cols incl. tools_schema, generated_code, required_secrets, allowedDomains, status enum, verified, fork_count). - mcp_servers.template_id FK + idx for fork lookup. - @bmm/types: SpecEdit unchanged, CreateServerInput accepts optional templateId. - preview-cache.ts: new cachePrebuiltCode/loadPrebuiltCode for storing the template's full rendered server.ts alongside the spec. Generator worker detects this and skips the render step — uses the audited pre-built code verbatim. Banned-pattern re-scan at publish time. - routes/templates.ts: 5 public/auth routes + 2 admin routes. Banned-pattern re-scan before publish. Slug auto-uniqued. forkCount atomic-increment via SQL. UI: - /templates marketplace with trending/top/newest tabs, category filter, search. Cards show forks + live count + author + verified badge. - /templates/[slug] full detail with tools, credentials-with-hints, expandable code preview, fork CTA, ownership + stats sidebar, 'forking is safe' explainer. - /servers/new?template=slug — wizard auto-jumps to Step 2 with template spec pre-filled, fork banner at top with link back to template. - /servers/[id] new Publish tab with title, category, descriptions, per-secret hint fields (description + howToGetUrl per UPPER_SNAKE_CASE key). - /admin/templates moderation with verify/hide/takedown actions. - Marketing nav now includes /templates. Verified end-to-end: - Published Echo Demo Template from marco@test.local's live server - Marketplace lists it correctly with stats - Detail page renders with all sections - Fork CTA navigates to wizard with ?template= param - Wizard skips Step 1, shows fork banner, pre-fills spec - Build succeeds in ~10s (cached spec + prebuilt code path skips Claude AND render), container live on :4109 with proper OAuth 401 → token → 200 flow - DB: templates.fork_count=1, activeDeployments=1, mcp_servers.template_id populated on the fork - /admin/templates shows the new template with verify/hide/takedown controls
2026-05-19 23:22:35 +02:00
<select
value={category}
onChange={(e) => setCategory(e.target.value)}
className="h-7 rounded-md border border-[--color-border] bg-[--color-bg-subtle] px-2 text-[12.5px] focus:border-[--color-accent] focus:outline-none"
>
<option value="">All categories</option>
{categories.map((c) => (
<option key={c} value={c}>
{c}
</option>
))}
</select>
<div className="flex-1" />
<Input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search…"
className="w-60"
/>
</div>
feat(marketplace): dashboard nav link + My-templates filter The logged-in user can now reach the marketplace and filter to their own templates. Dashboard nav: - Added 'Marketplace' item (Overview · Servers · Marketplace · Audit · Settings). /templates page — login-aware: - Detects session via /v1/auth/me. Logged-in users get a 'Dashboard' + '+ New server' header instead of 'Home' + 'Start building'. - New [All templates | My templates] scope toggle, shown only when logged in. - 'My templates' loads GET /v1/templates/mine and shows EVERY status the user owns (public / hidden / draft / takedown) with a colored status badge on each card — so a template you unshared doesn't appear to have vanished. - Sort tabs (trending/top/newest) hide in 'mine' scope — meaningless for a handful of own templates. Category filter + search still apply (client-side). - Takedown cards link to the source server's Publish tab instead of the detail route (which 410s); everything else opens the detail page. Backend: - GET /v1/templates/mine (requireAuth) — all own templates, any status, registered before /:slug so the static route always wins the match. - GET /v1/templates/:slug — now does an optional session check: the OWNER can view their own hidden/draft template (so a 'My templates' card click never dead-ends in a 404). takedown stays 410 for everyone, owner included — that's an admin decision, not the owner's to reverse. Detail page: - Fork CTA is gated on status === 'public'. For a non-public template the owner sees an amber 'not forkable — re-share from the Publish tab' notice plus a 'Manage in server' link, instead of a Fork button that would fail silently. Verified: - GET /v1/templates/mine → marco's 1 template; 401 without auth - Owner GET of a hidden template → 200 status:hidden; anon → 404 - Dashboard nav shows Marketplace (screenshot) - /templates 'My templates' toggle → only own template, public badge, sort tabs hidden (screenshot)
2026-05-20 17:18:58 +02:00
{!visible && <p className="mono text-[12px] text-[--color-fg-muted]">Loading</p>}
feat(marketplace): template publish + fork + voting/ranking + admin moderation What this enables: - A user builds an MCP server. If others would benefit, they click 'Publish as template' on their server detail page. The spec + pre-rendered TypeScript snapshot is preserved. - Visitors browse /templates, filter by category, sort by trending/top/newest. Each template card shows fork count + active deployment count as natural manipulation-resistant popularity signal. - /templates/[slug] shows the full plan: tool list with input schemas, required-credential explanations (with 'how to get one' deep links), and a collapsible code preview so users can audit before forking. - Fork is one click → /servers/new?template=slug. The wizard skips Step 1 and pre-fills Step 2 with the template's parsed spec. Forker only fills in their own credentials. mcp_servers.template_id is recorded; template.fork_count is bumped atomically. Each fork gets its own isolated container with its own port, its own AES-256 secrets — the template author has zero visibility into the fork's traffic or data. - Admin /admin/templates moderation: verify quality templates (shows shield badge in marketplace), hide low-effort ones, takedown anything malicious. Takedowns cascade-pause every fork container — owners must re-deploy. Why template+fork instead of shared-container: - Shared containers would mean the publisher's quota + their secrets + their logs are exposed to forkers. Bad ergonomics, bad security, bad ownership. - Templates/forks decouple the spec (shared, vouched-for) from the runtime (isolated per user). Network-effect moat without the trust collapse. Why no 5-star voting in v1: - Manipulation-anfällig, empty lists without adoption. We use fork count + active deploys + verified badge. Trending algorithm: score = (activeDeploys * 3 + forks) / sqrt(ageDays + 1) Real signal, no brigading attack surface. Backend: - New schema: templates table (16 cols incl. tools_schema, generated_code, required_secrets, allowedDomains, status enum, verified, fork_count). - mcp_servers.template_id FK + idx for fork lookup. - @bmm/types: SpecEdit unchanged, CreateServerInput accepts optional templateId. - preview-cache.ts: new cachePrebuiltCode/loadPrebuiltCode for storing the template's full rendered server.ts alongside the spec. Generator worker detects this and skips the render step — uses the audited pre-built code verbatim. Banned-pattern re-scan at publish time. - routes/templates.ts: 5 public/auth routes + 2 admin routes. Banned-pattern re-scan before publish. Slug auto-uniqued. forkCount atomic-increment via SQL. UI: - /templates marketplace with trending/top/newest tabs, category filter, search. Cards show forks + live count + author + verified badge. - /templates/[slug] full detail with tools, credentials-with-hints, expandable code preview, fork CTA, ownership + stats sidebar, 'forking is safe' explainer. - /servers/new?template=slug — wizard auto-jumps to Step 2 with template spec pre-filled, fork banner at top with link back to template. - /servers/[id] new Publish tab with title, category, descriptions, per-secret hint fields (description + howToGetUrl per UPPER_SNAKE_CASE key). - /admin/templates moderation with verify/hide/takedown actions. - Marketing nav now includes /templates. Verified end-to-end: - Published Echo Demo Template from marco@test.local's live server - Marketplace lists it correctly with stats - Detail page renders with all sections - Fork CTA navigates to wizard with ?template= param - Wizard skips Step 1, shows fork banner, pre-fills spec - Build succeeds in ~10s (cached spec + prebuilt code path skips Claude AND render), container live on :4109 with proper OAuth 401 → token → 200 flow - DB: templates.fork_count=1, activeDeployments=1, mcp_servers.template_id populated on the fork - /admin/templates shows the new template with verify/hide/takedown controls
2026-05-19 23:22:35 +02:00
{visible && visible.length === 0 && (
<div className="panel p-12 text-center">
feat(marketplace): dashboard nav link + My-templates filter The logged-in user can now reach the marketplace and filter to their own templates. Dashboard nav: - Added 'Marketplace' item (Overview · Servers · Marketplace · Audit · Settings). /templates page — login-aware: - Detects session via /v1/auth/me. Logged-in users get a 'Dashboard' + '+ New server' header instead of 'Home' + 'Start building'. - New [All templates | My templates] scope toggle, shown only when logged in. - 'My templates' loads GET /v1/templates/mine and shows EVERY status the user owns (public / hidden / draft / takedown) with a colored status badge on each card — so a template you unshared doesn't appear to have vanished. - Sort tabs (trending/top/newest) hide in 'mine' scope — meaningless for a handful of own templates. Category filter + search still apply (client-side). - Takedown cards link to the source server's Publish tab instead of the detail route (which 410s); everything else opens the detail page. Backend: - GET /v1/templates/mine (requireAuth) — all own templates, any status, registered before /:slug so the static route always wins the match. - GET /v1/templates/:slug — now does an optional session check: the OWNER can view their own hidden/draft template (so a 'My templates' card click never dead-ends in a 404). takedown stays 410 for everyone, owner included — that's an admin decision, not the owner's to reverse. Detail page: - Fork CTA is gated on status === 'public'. For a non-public template the owner sees an amber 'not forkable — re-share from the Publish tab' notice plus a 'Manage in server' link, instead of a Fork button that would fail silently. Verified: - GET /v1/templates/mine → marco's 1 template; 401 without auth - Owner GET of a hidden template → 200 status:hidden; anon → 404 - Dashboard nav shows Marketplace (screenshot) - /templates 'My templates' toggle → only own template, public badge, sort tabs hidden (screenshot)
2026-05-20 17:18:58 +02:00
{scope === 'mine' ? (
<>
<p className="text-[14px] text-[--color-fg]">You haven&apos;t published any templates.</p>
<p className="mt-1 text-[12.5px] text-[--color-fg-muted]">
Build a server, then tick <span className="mono">Share as template</span> on the
done screen or use the Publish tab on any live server.
</p>
</>
) : (
<>
<p className="text-[14px] text-[--color-fg]">No templates yet.</p>
<p className="mt-1 text-[12.5px] text-[--color-fg-muted]">
Build a server you&apos;re proud of and share it to the marketplace.
</p>
</>
)}
feat(marketplace): template publish + fork + voting/ranking + admin moderation What this enables: - A user builds an MCP server. If others would benefit, they click 'Publish as template' on their server detail page. The spec + pre-rendered TypeScript snapshot is preserved. - Visitors browse /templates, filter by category, sort by trending/top/newest. Each template card shows fork count + active deployment count as natural manipulation-resistant popularity signal. - /templates/[slug] shows the full plan: tool list with input schemas, required-credential explanations (with 'how to get one' deep links), and a collapsible code preview so users can audit before forking. - Fork is one click → /servers/new?template=slug. The wizard skips Step 1 and pre-fills Step 2 with the template's parsed spec. Forker only fills in their own credentials. mcp_servers.template_id is recorded; template.fork_count is bumped atomically. Each fork gets its own isolated container with its own port, its own AES-256 secrets — the template author has zero visibility into the fork's traffic or data. - Admin /admin/templates moderation: verify quality templates (shows shield badge in marketplace), hide low-effort ones, takedown anything malicious. Takedowns cascade-pause every fork container — owners must re-deploy. Why template+fork instead of shared-container: - Shared containers would mean the publisher's quota + their secrets + their logs are exposed to forkers. Bad ergonomics, bad security, bad ownership. - Templates/forks decouple the spec (shared, vouched-for) from the runtime (isolated per user). Network-effect moat without the trust collapse. Why no 5-star voting in v1: - Manipulation-anfällig, empty lists without adoption. We use fork count + active deploys + verified badge. Trending algorithm: score = (activeDeploys * 3 + forks) / sqrt(ageDays + 1) Real signal, no brigading attack surface. Backend: - New schema: templates table (16 cols incl. tools_schema, generated_code, required_secrets, allowedDomains, status enum, verified, fork_count). - mcp_servers.template_id FK + idx for fork lookup. - @bmm/types: SpecEdit unchanged, CreateServerInput accepts optional templateId. - preview-cache.ts: new cachePrebuiltCode/loadPrebuiltCode for storing the template's full rendered server.ts alongside the spec. Generator worker detects this and skips the render step — uses the audited pre-built code verbatim. Banned-pattern re-scan at publish time. - routes/templates.ts: 5 public/auth routes + 2 admin routes. Banned-pattern re-scan before publish. Slug auto-uniqued. forkCount atomic-increment via SQL. UI: - /templates marketplace with trending/top/newest tabs, category filter, search. Cards show forks + live count + author + verified badge. - /templates/[slug] full detail with tools, credentials-with-hints, expandable code preview, fork CTA, ownership + stats sidebar, 'forking is safe' explainer. - /servers/new?template=slug — wizard auto-jumps to Step 2 with template spec pre-filled, fork banner at top with link back to template. - /servers/[id] new Publish tab with title, category, descriptions, per-secret hint fields (description + howToGetUrl per UPPER_SNAKE_CASE key). - /admin/templates moderation with verify/hide/takedown actions. - Marketing nav now includes /templates. Verified end-to-end: - Published Echo Demo Template from marco@test.local's live server - Marketplace lists it correctly with stats - Detail page renders with all sections - Fork CTA navigates to wizard with ?template= param - Wizard skips Step 1, shows fork banner, pre-fills spec - Build succeeds in ~10s (cached spec + prebuilt code path skips Claude AND render), container live on :4109 with proper OAuth 401 → token → 200 flow - DB: templates.fork_count=1, activeDeployments=1, mcp_servers.template_id populated on the fork - /admin/templates shows the new template with verify/hide/takedown controls
2026-05-19 23:22:35 +02:00
</div>
)}
{visible && visible.length > 0 && (
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
{visible.map((t) => (
feat(marketplace): dashboard nav link + My-templates filter The logged-in user can now reach the marketplace and filter to their own templates. Dashboard nav: - Added 'Marketplace' item (Overview · Servers · Marketplace · Audit · Settings). /templates page — login-aware: - Detects session via /v1/auth/me. Logged-in users get a 'Dashboard' + '+ New server' header instead of 'Home' + 'Start building'. - New [All templates | My templates] scope toggle, shown only when logged in. - 'My templates' loads GET /v1/templates/mine and shows EVERY status the user owns (public / hidden / draft / takedown) with a colored status badge on each card — so a template you unshared doesn't appear to have vanished. - Sort tabs (trending/top/newest) hide in 'mine' scope — meaningless for a handful of own templates. Category filter + search still apply (client-side). - Takedown cards link to the source server's Publish tab instead of the detail route (which 410s); everything else opens the detail page. Backend: - GET /v1/templates/mine (requireAuth) — all own templates, any status, registered before /:slug so the static route always wins the match. - GET /v1/templates/:slug — now does an optional session check: the OWNER can view their own hidden/draft template (so a 'My templates' card click never dead-ends in a 404). takedown stays 410 for everyone, owner included — that's an admin decision, not the owner's to reverse. Detail page: - Fork CTA is gated on status === 'public'. For a non-public template the owner sees an amber 'not forkable — re-share from the Publish tab' notice plus a 'Manage in server' link, instead of a Fork button that would fail silently. Verified: - GET /v1/templates/mine → marco's 1 template; 401 without auth - Owner GET of a hidden template → 200 status:hidden; anon → 404 - Dashboard nav shows Marketplace (screenshot) - /templates 'My templates' toggle → only own template, public badge, sort tabs hidden (screenshot)
2026-05-20 17:18:58 +02:00
<TemplateCard key={t.id} t={t} showStatus={scope === 'mine'} />
feat(marketplace): template publish + fork + voting/ranking + admin moderation What this enables: - A user builds an MCP server. If others would benefit, they click 'Publish as template' on their server detail page. The spec + pre-rendered TypeScript snapshot is preserved. - Visitors browse /templates, filter by category, sort by trending/top/newest. Each template card shows fork count + active deployment count as natural manipulation-resistant popularity signal. - /templates/[slug] shows the full plan: tool list with input schemas, required-credential explanations (with 'how to get one' deep links), and a collapsible code preview so users can audit before forking. - Fork is one click → /servers/new?template=slug. The wizard skips Step 1 and pre-fills Step 2 with the template's parsed spec. Forker only fills in their own credentials. mcp_servers.template_id is recorded; template.fork_count is bumped atomically. Each fork gets its own isolated container with its own port, its own AES-256 secrets — the template author has zero visibility into the fork's traffic or data. - Admin /admin/templates moderation: verify quality templates (shows shield badge in marketplace), hide low-effort ones, takedown anything malicious. Takedowns cascade-pause every fork container — owners must re-deploy. Why template+fork instead of shared-container: - Shared containers would mean the publisher's quota + their secrets + their logs are exposed to forkers. Bad ergonomics, bad security, bad ownership. - Templates/forks decouple the spec (shared, vouched-for) from the runtime (isolated per user). Network-effect moat without the trust collapse. Why no 5-star voting in v1: - Manipulation-anfällig, empty lists without adoption. We use fork count + active deploys + verified badge. Trending algorithm: score = (activeDeploys * 3 + forks) / sqrt(ageDays + 1) Real signal, no brigading attack surface. Backend: - New schema: templates table (16 cols incl. tools_schema, generated_code, required_secrets, allowedDomains, status enum, verified, fork_count). - mcp_servers.template_id FK + idx for fork lookup. - @bmm/types: SpecEdit unchanged, CreateServerInput accepts optional templateId. - preview-cache.ts: new cachePrebuiltCode/loadPrebuiltCode for storing the template's full rendered server.ts alongside the spec. Generator worker detects this and skips the render step — uses the audited pre-built code verbatim. Banned-pattern re-scan at publish time. - routes/templates.ts: 5 public/auth routes + 2 admin routes. Banned-pattern re-scan before publish. Slug auto-uniqued. forkCount atomic-increment via SQL. UI: - /templates marketplace with trending/top/newest tabs, category filter, search. Cards show forks + live count + author + verified badge. - /templates/[slug] full detail with tools, credentials-with-hints, expandable code preview, fork CTA, ownership + stats sidebar, 'forking is safe' explainer. - /servers/new?template=slug — wizard auto-jumps to Step 2 with template spec pre-filled, fork banner at top with link back to template. - /servers/[id] new Publish tab with title, category, descriptions, per-secret hint fields (description + howToGetUrl per UPPER_SNAKE_CASE key). - /admin/templates moderation with verify/hide/takedown actions. - Marketing nav now includes /templates. Verified end-to-end: - Published Echo Demo Template from marco@test.local's live server - Marketplace lists it correctly with stats - Detail page renders with all sections - Fork CTA navigates to wizard with ?template= param - Wizard skips Step 1, shows fork banner, pre-fills spec - Build succeeds in ~10s (cached spec + prebuilt code path skips Claude AND render), container live on :4109 with proper OAuth 401 → token → 200 flow - DB: templates.fork_count=1, activeDeployments=1, mcp_servers.template_id populated on the fork - /admin/templates shows the new template with verify/hide/takedown controls
2026-05-19 23:22:35 +02:00
))}
</div>
)}
</main>
<footer className="border-t border-[--color-border] py-8">
<div className="mx-auto max-w-6xl px-6 text-[12px] text-[--color-fg-subtle]">
Every template is isolated: forking creates your own container with your own secrets.
</div>
</footer>
</div>
);
}
feat(marketplace): dashboard nav link + My-templates filter The logged-in user can now reach the marketplace and filter to their own templates. Dashboard nav: - Added 'Marketplace' item (Overview · Servers · Marketplace · Audit · Settings). /templates page — login-aware: - Detects session via /v1/auth/me. Logged-in users get a 'Dashboard' + '+ New server' header instead of 'Home' + 'Start building'. - New [All templates | My templates] scope toggle, shown only when logged in. - 'My templates' loads GET /v1/templates/mine and shows EVERY status the user owns (public / hidden / draft / takedown) with a colored status badge on each card — so a template you unshared doesn't appear to have vanished. - Sort tabs (trending/top/newest) hide in 'mine' scope — meaningless for a handful of own templates. Category filter + search still apply (client-side). - Takedown cards link to the source server's Publish tab instead of the detail route (which 410s); everything else opens the detail page. Backend: - GET /v1/templates/mine (requireAuth) — all own templates, any status, registered before /:slug so the static route always wins the match. - GET /v1/templates/:slug — now does an optional session check: the OWNER can view their own hidden/draft template (so a 'My templates' card click never dead-ends in a 404). takedown stays 410 for everyone, owner included — that's an admin decision, not the owner's to reverse. Detail page: - Fork CTA is gated on status === 'public'. For a non-public template the owner sees an amber 'not forkable — re-share from the Publish tab' notice plus a 'Manage in server' link, instead of a Fork button that would fail silently. Verified: - GET /v1/templates/mine → marco's 1 template; 401 without auth - Owner GET of a hidden template → 200 status:hidden; anon → 404 - Dashboard nav shows Marketplace (screenshot) - /templates 'My templates' toggle → only own template, public badge, sort tabs hidden (screenshot)
2026-05-20 17:18:58 +02:00
function TemplateCard({ t, showStatus }: { t: Template; showStatus: boolean }) {
// takedown templates can't be opened (the detail route 410s); link to the
// server's Publish tab so the owner can see why. Everything else opens detail.
const href =
t.status === 'takedown' && t.sourceServerId
? `/servers/${t.sourceServerId}`
: `/templates/${t.slug}`;
return (
<Link
href={href}
className="panel flex flex-col p-4 transition-colors duration-200 hover:border-[--color-border-strong]"
>
<div className="flex items-start justify-between gap-2">
<div className="flex items-center gap-1.5">
<h2 className="text-[14.5px] font-semibold tracking-tight">{t.title}</h2>
{t.verified && (
<span
className="inline-flex items-center gap-0.5 rounded-full border border-[--color-accent]/40 bg-[--color-accent]/10 px-1.5 py-0.5 text-[9.5px] font-medium text-[--color-accent]"
title="Verified by BuildMyMCPServer"
>
<ShieldCheck size={9} /> verified
</span>
)}
</div>
{showStatus ? (
<span
className={cn(
'mono rounded-full border px-1.5 py-0.5 text-[9.5px]',
STATUS_STYLE[t.status],
)}
>
{t.status}
</span>
) : (
<span className="mono rounded-full border border-[--color-border] bg-[--color-bg-subtle] px-1.5 py-0.5 text-[10px] text-[--color-fg-subtle]">
{t.category}
</span>
)}
</div>
<p className="mt-2 flex-1 text-[12.5px] leading-relaxed text-[--color-fg-muted]">
{t.shortDescription}
</p>
<div className="mt-4 flex items-center justify-between text-[11px] text-[--color-fg-subtle]">
<div className="flex items-center gap-3">
<span className="inline-flex items-center gap-1 mono" title="Total forks">
<GitFork size={11} />
{t.forkCount}
</span>
<span className="inline-flex items-center gap-1 mono" title="Active deployments">
<Activity size={11} />
{t.activeDeployments}
</span>
</div>
<span className="truncate">
{showStatus ? t.category : `by ${t.ownerName ?? t.ownerOrgName ?? 'anonymous'}`}
</span>
</div>
</Link>
);
}