Prompts were sent to the model with no secret scan, so a pasted API key would leak to the LLM. Added findSecretInPrompt in @bmm/types (tight provider-key patterns: Anthropic/OpenAI/GitHub/AWS/Google/Slack/Stripe/JWT/private-key) shared by both sides. The web wizard blocks before sending with a clear message; the API preview and preview-stream endpoints reject with secret_in_prompt as the hard guarantee. Credential VALUES already never touched the model - they are entered in the separate encrypted step 2; this closes the remaining leak path where a user pastes a key into the prompt itself.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- /oauth/register: drop resource_required check, accept generic
registrations (Claude Desktop omits resource in DCR body per spec).
serverId stored as NULL; /authorize still enforces org-ownership
+ access-token aud claim still pinned to resource. Fixes Claude
Desktop DCR failure (ofid_d7e39530c109fa7f).
- /oauth/authorize: skip strict server.id check when client.serverId
is NULL (generic client); org check remains the security boundary.
- schema: oauth_clients.server_id no longer NOT NULL.
- migration 0002: ALTER COLUMN server_id DROP NOT NULL (already
applied on prod).
- install-snippets: add Claude Code (CLI), VS Code, Codex, raw URL
tabs. Claude Desktop now shows form-field values (Name / Remote MCP
Server URL / OAuth Client ID / Secret) matching the new Custom
Connector UI instead of the obsolete JSON config.
- types: InstallTarget enum extended.
- hero-video: clicking the audio toggle restarts the video from
frame 0 so unmute aligns with the spoken opening.
- marketing: drop em-dashes from rendered copy.
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
The wizard's confirm step is no longer read-only. Users can refine what Claude
parsed before committing to a build.
Backend:
- @bmm/types adds SpecEdit (tools[name,description,inputSchema] + requiredSecrets);
CreateServerInput accepts an optional specEdit alongside previewId.
- Servers create endpoint: when specEdit is provided, loads cached spec from Redis,
index-merges the edits in (keeping LLM-generated implementations untouched),
re-validates via GeneratorSpec, re-runs the banned-pattern scan, overwrites the
Redis cache so the worker reads the user's version. Refuses with
preview_expired/tool_count_mismatch/banned_pattern on safety failures.
- New overwriteSpec() helper in preview-cache.
Frontend:
- Step 2 renders each tool as an editable card: name input, description textarea,
JSON schema textarea with parse-on-keystroke validation (inline error if invalid).
- Required secrets list is editable: keys via uppercase-snake-case input, +Add /
remove buttons, secret values kept in sync when keys are renamed.
- Reset-to-AI-suggestion button appears when edits are dirty.
- Pre-submit validation: schema must parse, secret keys must match UPPER_SNAKE_CASE,
required secret values must be provided.
- Warning copy: 'Renaming parameters may require an Iterate after build — the
existing impl references the original names.'
Verified end-to-end via browser smoke test: edited description + renamed tool
landed correctly in mcp_servers.tools_schema and in the live container at :4107.
Implementation field preserved from the original cached spec.
- POST /v1/servers/preview runs Claude synchronously, validates output, caches spec
in Redis under preview:<id> with 5min TTL, returns previewId+spec+detectedSecrets.
- POST /v1/servers accepts optional previewId; worker reuses the cached spec if
the entry is still present, otherwise regenerates fresh. Skips the second
Claude round-trip (~30s saved on the demoable path).
- audit() helper writes auth.login, auth.logout, server.create, server.iterate,
server.delete to audit_log with ip, metadata, resourceId.
- GET /v1/me/org returns organization + members list for the settings page.
- GET /v1/audit?limit=&action=&resourceType= returns scoped audit entries.
- Bump @modelcontextprotocol/sdk from 1.0.4 to 1.29.0 in runner-template
(1.0.4 has no McpServer or StreamableHTTPServerTransport — file not found at runtime).
- Bump zod to 3.25.76 across workspace to satisfy modern SDK peer dep.
- Split OAUTH_ISSUER (canonical, host-reachable) from CONTROL_PLANE_URL (container-reachable for JWKS).
Runner verifies iss against OAUTH_ISSUER; fetches JWKS from CONTROL_PLANE_URL.
Both API and runner now agree on http://localhost:4000/oauth as the issuer in dev.
- Move postgres host port 5432 to 5440, redis 6379 to 6390 to avoid collisions with
native installs on the dev machine.
- Move web from 3000 to 3001 (3000 occupied by Gitea on dev machine).
- Drop pino-pretty transport from API to avoid runtime require of an unbundled dep.
- Cast build_logs.level (varchar) to BuildEvent's literal union in WS replay path.
- Remove unused reqBase helper in oauth.ts.