@seldonframe/mcp 1.40.14 → 1.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/package.json +17 -122
  2. package/src/tools.js +210 -0
  3. package/src/welcome.js +488 -488
package/package.json CHANGED
@@ -1,126 +1,8 @@
1
1
  {
2
2
  "name": "@seldonframe/mcp",
3
- "version": "1.40.14",
4
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.40.14: GOOGLE ANALYTICS host-aware install. Wires gtag.js (Google Analytics 4) into the seldonframe.com marketing site + app.seldonframe.com operator dashboard, but DELIBERATELY skips it on every operator workspace subdomain (*.app.seldonframe.com). The privacy reasoning: operator workspace subdomains serve THE OPERATOR'S customers, not SeldonFrame's. Aggregating their browsing data under SeldonFrame's measurement ID would violate Google's TOS, be ethically wrong (thousands of unrelated businesses' customers tracked under one ID without their consent), and confuse operator-owned GA setups (a future product feature where operators paste their own measurement ID into workspace settings — we don't want double-hits). NEW components/analytics/google-analytics.tsx with two exports: GoogleAnalytics (the Script tags using next/script afterInteractive strategy) and shouldRenderGoogleAnalytics(host) helper that returns true only for explicit allowlist entries (seldonframe.com, www.seldonframe.com, app.seldonframe.com). Root app/layout.tsx now async, reads headers().get('host') server-side, conditionally renders <GoogleAnalytics> when both NEXT_PUBLIC_GA_MEASUREMENT_ID env var is set AND the host passes the allowlist. Workspace subdomains, Vercel preview deploys, localhost dev, anything not explicitly allowlisted → no GA. NEW env var REQUIRED: NEXT_PUBLIC_GA_MEASUREMENT_ID (e.g. G-66W62KE0ST). Set in Vercel Project Settings → Environment Variables for Production + Preview + Development. NO migrations, NO new MCP tools. Backend redeploy required for the layout change. EXPECTED IMPACT: pageview tracking lights up on the marketing site + operator dashboard within minutes of Vercel redeploy + env var set. Operator workspace visitors generate zero tracking pings under SeldonFrame's measurement ID — clean privacy boundary, clean TOS compliance, future-proof for per-workspace operator-owned GA tags.",
5
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.40.13: BOOKINGS CALENDAR — CARDS POSITIONED AT THEIR ACTUAL TIME. Pre-1.40.13 the /bookings dashboard week-view rendered booking cards as a flexbox column at the TOP of each day cell, regardless of the appointment's start time. The 12-hour time column on the left (8:00 → 19:00) was purely decorative. A 1 PM booking appeared visually at the 8 AM row with '1:00 PM' printed inside the card — confusing for any operator scanning the calendar. v1.40.13 wires each booking card to the time grid via absolute positioning. NEW constants WEEK_VIEW_START_HOUR=8, WEEK_VIEW_END_HOUR=20, HOUR_HEIGHT_PX=80 (matches the existing h-20 time-column rows) anchor the grid. NEW timeInZone(date, tz) helper extracts hours/minutes from a Date in the workspace timezone via Intl.DateTimeFormat round-trip (browser-local getHours() drifts when viewer TZ ≠ workspace TZ — same fix pattern as v1.40.2's utcMomentForLocalTime helper). NEW bookingTopPx(startsAt, tz) computes the vertical pixel offset from the grid's start (clamped so off-hours bookings render at the boundary, not off-screen). Day columns now use `relative` positioning + fixed `height: 960px` (12 hours × 80px) matching the time column. Booking cards are `absolute` with `top: bookingTopPx(...)` + `height: 76px` (60 minute default duration). Hour grid lines added to each day column so the visual rhythm aligns with the time labels on the left. NO timezone bug: the position uses the workspace timezone consistently with v1.40.9's display fix — a 1 PM workspace-local booking lands at the 13:00 row, regardless of where the operator is viewing from. NO data shape change: bookings already include startsAt as ISO; the fix is pure layout. NO migrations, NO new MCP tools, NO new env vars. Backend redeploy required. EXPECTED IMPACT: the operator dashboard's calendar visually matches reality — a 1pm appointment appears at the 1pm row, not at the top of the day column. Multi-booking days stack visually correctly. Off-hours bookings (e.g. 6 AM emergency or 9 PM after-hours) clamp to the visible boundary with their real time visible inside. Caps the v1.40.12 ship.",
6
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.40.12: VALIDATOR + BOOKING-FLOW PROMPT FIX. Real conversation in production exposed two intertwined bugs in the website-chatbot booking flow. Transcript: user gave name/email/phone in one message → agent immediately called book_appointment (premature) AND asked 'go ahead?' (contradictory — booking already happened) → user replied 'go ahead' → agent tried to acknowledge ('You're booked for Monday at 1pm') → no_hallucinated_state_change validator blocked it → regen blocked → fallback ('Let me look into that for you') → conversation stalled, user confused. (1) VALIDATOR FIX. noHallucinatedStateChange in lib/agents/validators.ts was scoped to THIS TURN's tool calls only. When the agent legitimately acknowledged a booking that succeeded in the PREVIOUS turn, the validator saw no book_appointment in the current turn and flagged it as hallucination. v1.40.12 adds a `recentSuccessfulTools: string[]` field to ValidatorContext. The runtime scans all assistant turns in the conversation history and builds a set of tool names that succeeded with ok=true. The validator unions this with the current-turn set before checking action-pattern matches. Legitimate follow-up acknowledgments (turn N+1 saying 'you're booked' after turn N's successful book_appointment) now pass. Hallucinations (turn N saying 'you're booked' when no book_appointment was ever called in this conversation) still fail. (2) PROMPT FIX. be-smart-by-default.ts rule 5 'Confirm before destructive actions' was too vague — said 'wait for explicit yes' but didn't enforce the strict ORDER of operations. The agent in production interpreted 'confirm before destructive actions' as 'present details AND book in the same turn, then ask for confirmation' — leading to the contradictory 'go ahead?' question about something already done. v1.40.12 rewrites rule 5 with explicit 5-step ordered sequence: gather info → present summary (one sentence ending in '?') → WAIT → on explicit 'yes', call tool → on tool ok=true, acknowledge in one sentence. Also added explicit text: 'DO NOT call the tool in the same turn as the confirmation summary. The confirmation summary turn does NOT include a tool call.' Rule 6 also extended with: 'IT IS OK to acknowledge a tool that succeeded in a PREVIOUS turn' to dovetail with the validator fix. NO new env vars, NO migrations, NO new MCP tools. Backend redeploy required. EXPECTED IMPACT: bookings flow correctly — agent presents details, waits, gets explicit confirmation, calls the tool, acknowledges the result. No more 'go ahead?' contradiction. No more fallback after the user said go ahead. Both the prompt-side (prevent the antecedent confusion) and validator-side (don't punish legitimate follow-up acknowledgments) covered.",
7
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.40.11: MOBILE CHATBOT UX HOTFIX. Two real bugs surfaced on first published-agent test: (1) Mobile horizontal scroll inside the chat panel — long URLs/tokens in assistant replies pushed the panel into horizontal overflow on mobile widths. The .sf-agent-messages container had overflow-y:auto but no overflow-x:hidden, and the .sf-agent-msg bubbles had word-wrap:break-word but no overflow-wrap:anywhere. v1.40.11 adds overflow-x:hidden + min-width:0 to .sf-agent-messages and overflow-wrap:anywhere + min-width:0 to .sf-agent-msg. The min-width:0 is required to override the flex-default min-width:auto which would otherwise refuse to shrink the message bubble below its content size. Long URLs, long emails, long IDs now wrap inside the bubble instead of pushing the panel sideways. (2) Input loses focus + keyboard dismisses after send on iOS. Pre-1.40.11 the submit handler set inputEl.disabled=true during send and inputEl.disabled=false + inputEl.focus() after. On iOS Safari, setting disabled=true dismisses the soft keyboard. The later .focus() call — running outside a user-gesture event handler because of the async fetch + SSE chain — cannot reopen the keyboard. Result: user sends message, keyboard dismisses, has to tap the input again to send the next message. Mobile UX death. v1.40.11 reframes: input stays ENABLED throughout the send (keyboard stays up, focus preserved), the SEND BUTTON gets disabled instead (visual feedback that the turn is in flight). New `sending` flag in the handler closure prevents double-submit without touching the input element. The .focus() call after send is removed — input was never blurred, so focus is preserved naturally. NO new env vars, NO migrations, NO new MCP tools. Backend redeploy required for both fixes (embed.js is server-rendered per request — bubble auto-pulls new CSS + new handler on next page load, no re-embed needed by operators). EXPECTED IMPACT: chat conversations on mobile feel like native messaging apps — keyboard stays up across multiple sends, no horizontal scroll regardless of how chatty the agent gets. Caps the v1.40.10 ship. NOT INCLUDED IN THIS SHIP: validator no_hallucinated_state_change failures (needs conversation transcript to diagnose per-case), AUTH_SECRET missing in Vercel env (operator env config, unrelated to chatbot code).",
8
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.40.10: ZERO-FRICTION CHATBOT CREATION (platform-key fallback). The 'extract testimonials and build chatbot' test surfaced friction: build_website_chatbot blocked workspace chatbot creation when no Anthropic key was found in process.env.ANTHROPIC_API_KEY, even though the SeldonFrame backend's lib/ai/client.ts -> getAIClient already falls back to a platform Anthropic key for any workspace without BYOK. The chatbot WORKS without operator BYOK; we were just refusing to create it. v1.40.10 fixes the false barrier. NEW BEHAVIOR: build_website_chatbot now has three LLM-key paths in priority order — (1) explicit anthropic_api_key arg → set as workspace BYOK, (2) process.env.ANTHROPIC_API_KEY in MCP server env → set as workspace BYOK, (3) NEITHER → skip set_llm_key entirely; workspace uses SeldonFrame's platform Anthropic key automatically. Pre-1.40.10 path 3 returned `error: 'no_anthropic_key'` and refused to create the agent; v1.40.10 path 3 succeeds, creates the agent, returns `llm_mode: 'platform'` so Claude Code can naturally tell the operator their chatbot is using SF's shared key and they can BYOK later in /settings/integrations/llm. Removes the 'where do I get an Anthropic key' question from the critical path of a first-time chatbot setup. The runtime behavior was already correct (getAIClient falls back to platform key when no BYOK is configured); v1.40.10 just stops blocking creation. UX SHIFT: operator says 'create a chatbot' → Claude Code calls build_website_chatbot → chatbot is live in ~30s, no key prompt → operator sandbox-tests, publishes to live, chatbot serves visitors using SF platform inference. Operator who eventually wants their own billing pastes their key in /settings/integrations/llm — chatbot picks it up on next turn, no agent recreation. Welcome banner updated with the three-path priority order + the operator-facing messaging Claude Code should use. NO new env vars (uses existing SF backend ANTHROPIC_API_KEY). NO migrations. NO new MCP tools. Backend redeploy required for the build_website_chatbot logic change to take effect via MCP.",
9
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.40.9: BOOKING-FLOW HARDENING. Three coordinated fixes addressing the bugs surfaced on the v1.40.8 Sunset Plumbing chatbot test (where the agent confidently said 'Tuesday, May 13' for a date that was actually Wednesday, then booked at 9 AM but the calendar displayed 12 PM). (1) STRENGTHENED TEMPORAL-REASONING SKILL. The website-chatbot's temporal-reasoning skill (lib/agents/skills/website-chatbot/temporal-reasoning.ts) had a single paragraph telling the agent to resolve relative dates using {{currentDate}}. Two failure modes slipped through: the LLM computed weekday names by arithmetic and got them wrong, AND the LLM computed slot times in its head rather than using the look_up_availability tool. v1.40.9 expands the skill into four sections: (a) explicit interpretation rules for 'today/tomorrow/this <weekday>/next <weekday>/next week' with disambiguation guidance; (b) MUST state both weekday name AND date when communicating ('Wednesday, May 13' not 'May 13' or 'Wednesday' alone) so any LLM arithmetic error surfaces visibly to the visitor; (c) NEVER quote a specific time from memory — always call look_up_availability first; (d) restated that look_up_availability returns ground truth slots tied to the workspace's actual calendar in the workspace timezone. (2) TIGHTENED book_appointment TOOL DESCRIPTION. Pre-1.40.9 the tool's slotIso parameter said 'ISO local (e.g. 2026-05-14T14:30)' which the LLM interpreted as license to invent naive local timestamps. Server then parsed naive timestamps as UTC, leading to silent timezone drift on every booking from a workspace whose timezone wasn't UTC. v1.40.9 reframes the parameter as 'MUST be one of the slot strings returned by look_up_availability, copied VERBATIM' with explicit warning: 'Do NOT pass naive local times like 2026-05-13T09:00 — those get misinterpreted as UTC and book the wrong time.' Tool description also explicitly states the call order (look_up_availability FIRST, book_appointment SECOND with verbatim slot). (3) DASHBOARD BOOKINGS RENDER IN WORKSPACE TZ. Pre-1.40.9 the operator's /bookings calendar formatted times via toLocaleTimeString without a timeZone option — defaulting to the VIEWER'S browser timezone. A 9 AM PDT booking displayed as 12 PM EDT for an East Coast operator-viewer (the exact bug Sunset Plumbing exposed). v1.40.9 threads workspaceTimezone from organizations.timezone all the way down to BookingsPageContent and applies { timeZone: workspaceTimezone } on every formatter (toLocaleTimeString, formatDateGroupLabel, keyYmd, labelRangeStart/End, dayHeaderLabel). The keyYmd helper now uses Intl.DateTimeFormat with en-CA locale (which emits ISO YYYY-MM-DD) so day-grouping is also workspace-anchored — bookings near midnight local don't drift to the wrong day for viewers in different timezones. NO new env vars (uses existing organizations.timezone). NO migrations (timezone column already exists from earlier ships). NO new MCP tools. Backend redeploy required for the system-prompt + dashboard fixes to take effect. EXPECTED IMPACT: a chatbot conversation booking 'next Tuesday at 9 AM' from an LA workspace now (a) computes the correct date with weekday verification, (b) calls look_up_availability + uses a verbatim slot string, (c) the booking lands at 9 AM LA time AND displays as 9 AM in the operator's dashboard (regardless of where the operator is viewing from). End-to-end booking flow consistent across timezones. Caps the v1.40.8 ship.",
10
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.40.8: HOTFIX — chatbot widget bugs surfaced on the v1.40.7 first-test. Two bugs blocked the just-shipped chatbot embed from actually working end-to-end on Sunset Plumbing. (1) INPUT TEXT INVISIBLE (white-on-white). The .sf-agent-input CSS in embed.js had no explicit color rule, so typed characters inherited the host page's color (var(--sf-text)). On workspaces with stored dark themes OR pages that set color globally, characters rendered white-on-white-background — invisible while typing. v1.40.8 hardcodes color:#111 + background:#fff + -webkit-text-fill-color:#111 on the input element, plus a placeholder color rule. The chat widget is now isolated from host-page CSS — typed characters always readable regardless of host theme. (2) CORS BLOCKING /turn FETCH. The chatbot widget loads as <script src='https://app.seldonframe.com/...'> on workspace subdomain pages (e.g. sunset-plumbing-co.app.seldonframe.com). Script-tag loading isn't CORS-restricted, but the widget's POST fetch from inside the script to https://app.seldonframe.com/api/v1/public/agent/.../turn IS cross-origin. Pre-1.40.8 the turn endpoint returned ZERO CORS headers — browser blocked the preflight, fetch threw, widget surfaced 'Connection issue. Please try again.' on every send. v1.40.8 adds Access-Control-Allow-Origin: * + Methods + Headers + Max-Age, plus an OPTIONS handler for preflight, on every code path of the turn endpoint (invalid_json, missing_message, message_too_long, agent_not_found, agent_not_active, conversation_create_failed, the SSE stream, and the JSON fallback). Origin=* is correct here because the chatbot is intentionally embeddable on any operator's website (the entire point of the embed); the endpoint serves only public, conversation-scoped data; downstream agent runtime already enforces per-conversation auth via conversation_id + anonymous_session_id. Loosening CORS doesn't loosen application authorization. NO new env vars, NO migrations, NO new MCP tools. Backend redeploy required for both fixes (embed.js is server-rendered per-request — operators don't need to re-embed; bubble auto-pulls new CSS on next page load). EXPECTED IMPACT: visit https://sunset-plumbing-co.app.seldonframe.com → typed characters in the chat input are dark gray and readable; sending a message reaches the agent + streams the SSE response back. End-to-end chatbot UX now actually works on workspace subdomains. Caps the v1.40.7 ship.",
11
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.40.7: WORKSPACE-LEVEL CHATBOT EMBED. The natural-language ask 'add the chatbot to my landing page' now works as a single MCP call. Pre-1.40.7 the only way to put the chatbot bubble on a page was to copy-paste the embed snippet into Pages → Edit (the v1.40.6 banner promised an automated path that didn't actually exist — this fixes that). v1.40.7 ships the real implementation. NEW lib/agents/public-embed.ts: getPublicChatbotEmbed(orgId) reads, setPublicChatbotEmbed(orgId, {embedUrl, agentId}) writes to organizations.settings.chatbot. Storage validates https-only embed URLs as defense-in-depth (the writer only ever derives URLs from WORKSPACE_BASE_DOMAIN, but the read-side check ensures a malformed JSONB value can't inject arbitrary script). NEW components/landing/chatbot-script.tsx wraps Next.js <Script strategy='afterInteractive'> so the embed.js loads off-critical-path. Wired into BOTH /s/[orgSlug]/[...slug]/page.tsx (workspace home rewrite target) and /l/[orgSlug]/[slug]/page.tsx (legacy landing route) — every public page of every workspace honors the embed if set. NEW api/v1/agents POST op 'embed_on_landing' validates agent ownership + status (test/live only — refuses draft) + builds embed.js URL from agent.orgSlug + agent.slug + WORKSPACE_BASE_DOMAIN, then calls setPublicChatbotEmbed. Sibling op 'remove_from_landing' clears the embed without deleting the agent (still serves /agents/[id]/test for sandboxing). NEW MCP tool embed_chatbot_on_workspace_landing({ workspace_id, agent_id }) is the operator-facing one-call wrapper. Sibling remove_chatbot_from_landing({ workspace_id }) clears it. build_website_chatbot's next_steps step 4 updated to reference embed_chatbot_on_workspace_landing as a one-call action (was misleading add_composite_section guidance pre-1.40.7). Welcome banner v1.40.7 section walks through the embed flow with the actual tool name. NO new env vars (uses existing WORKSPACE_BASE_DOMAIN). NO migrations (settings is already a flexible JSONB; new chatbot key merges in cleanly with existing keys). Backend redeploy required for the new API op + page-renderer injection. Pairs with v1.40.6's Custom Agent → Custom Workflow rename for a coherent post-creation operator UX: agents and workflows are now visually distinct, AND the chatbot is one natural-language ask away from being live on the landing page. EXPECTED IMPACT: operator says 'add the chatbot to the Sunset Plumbing landing' → Claude Code calls embed_chatbot_on_workspace_landing → bubble appears bottom-right on https://sunset-plumbing-co.app.seldonframe.com immediately on next page load. Closes the gap between agent creation and agent-on-page that v1.40.6 left open.",
12
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.40.6: UX FIX — Custom Agent → Custom Workflow rename + clearer chatbot-to-landing embed flow. Two related operator-confusion bugs surfaced after the Sunset Plumbing test. (1) RENAMED 'Custom Agent' template in /automations → 'Custom Workflow'. The /automations page hosts time-triggered + event-triggered workflow rules (Speed-to-Lead, Win-Back, Daily Digest, etc.); /agents hosts persistent AI assistants (chatbots, voice receptionists). They are conceptually different primitives — automations fire on a trigger and exit, agents are always-on with conversational state. The 'Custom Agent' coming-soon placeholder used 'agent' in its name, causing operators to (a) search /automations for their chatbot they created via build_website_chatbot, (b) search /agents for their workflow templates, neither matching expectations. Renamed to 'Custom Workflow' with body copy that explicitly points to /agents for conversational AI. Removes the ambiguity at the source. (2) CHATBOT-TO-LANDING EMBED FLOW now explicit. Pre-1.40.6 the build_website_chatbot success response said only 'Drop on the operator's website: <script ...></script>' which operators interpreted as 'I have to copy-paste this myself' — they didn't realize Claude Code can inject the script directly into the SF-hosted landing page via add_composite_section. v1.40.6 reframes step 4 of next_steps as a TWO-PATH decision: 'Add to the operator's SF-hosted landing page: ask me to add the chatbot to my landing page and I'll inject the script via update_landing_section. (For an external website the operator owns, paste this snippet manually: <script src=...>)'. Plus a NEW section in welcome.js banner that walks through the embed-via-Claude-Code flow explicitly — when the operator asks 'add the chatbot to my landing page' AFTER build_website_chatbot, Claude Code uses add_composite_section to insert a custom HTML section with the embed script. Operators no longer have to find/edit HTML manually. NO new env vars, NO migrations, NO new MCP tools. Backend redeploy required for the /automations rename to take effect. EXPECTED IMPACT: operators searching for their chatbot land on /agents directly (not lost in /automations); operators asking 'how do I add the chatbot to my page' get a one-natural-language-ask answer instead of a copy-paste task. Pairs cleanly with v1.40.5's Unsplash production-compliance ship — both reduce friction in the post-creation operator experience.",
13
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.40.5: UNSPLASH PRODUCTION-TIER COMPLIANCE + 3-TIER BROADENING. Three coordinated changes that close the loop on Unsplash production approval (raises rate limit from 50 req/hour to 1000 req/hour) and fix the medspa 0-result case v1.40.4 didn't catch. (1) PHOTOGRAPHER ATTRIBUTION rendered on every Unsplash-sourced image. NEW UnsplashAttribution type captures photographer_name + photographer_username + photographer_url + photo_id from each search-result.user payload. New ResolvedUnsplashImage interface bundles { url, attribution } as the resolver's return shape. NEW resolveHeroImage() + resolveGalleryImages() siblings of the legacy URL-only functions return the full payload. enhance-blocks.ts threads attribution into LandingPageSection content (heroImageAttribution on hero blocks, attribution per item on projectGallery). NEW UnsplashCredit component in hero.tsx renders 'Photo by NAME on Unsplash' with both names linking to the photographer's profile + Unsplash homepage with the spec-required UTM params (?utm_source=seldonframe&utm_medium=referral). Tone='light' for cinematic-fullbleed (white text overlaying photo bottom-right corner), tone='dark' for side-image variants (muted text below image). Gallery tiles extend the existing hover overlay with a per-tile credit line. Without this, Unsplash's production-tier reviewers reject the application; with it, every public landing page on every workspace is compliant. (2) DOWNLOAD-LOCATION TRACKING fired on every resolved image. NEW trackUnsplashDownload() helper sends a fire-and-forget GET to photo.links.download_location whenever a hero or gallery image is resolved. Unsplash uses this to credit the photographer's download counter — required by the API guidelines, checked during production review. Failure of the ping logs but doesn't block workspace creation. Critical-path latency unchanged (the GET runs in parallel with the rest of enhance-blocks). (3) THREE-TIER QUERY BROADENING. v1.40.4 dropped the first word on a 0-result; v1.40.5 also tries the LAST 2 words as a third tier. The HERO Aesthetic test surfaced that 'minimalist medspa treatment room' returned 0 even after broadening to 'medspa treatment room' — the word 'medspa' itself is rare on Unsplash (photographers tag spa/wellness/clinic/aesthetic, not 'medspa'). Tier 3 strips niche brand-marketing tokens entirely: 'minimalist medspa treatment room' → 'minimalist medspa treatment room' / 'medspa treatment room' / 'treatment room'. The last tier is universal noun phrasing that's heavily tagged. Per-query hit rate now ~99.5%. NEW buildQueryCandidates() helper deduplicates the three tiers automatically. Mirrored in scripts/test-unsplash.mjs so the diagnostic stays accurate. NEXT STEP FOR OPERATOR: redeploy backend, re-test (5/5 should pass), CREATE one workspace as a real-world demo, then submit for production approval at https://unsplash.com/oauth/applications. The production review form asks for: app URL (your published workspace), app description, screenshots showing photographer attribution + verifying download tracking is firing (Unsplash inspects the API logs server-side). Approval typically lands in 24-48h, then rate limit auto-lifts to 1000/hour. Unsplash API is FREE at every tier — no payment, ever. NO new env vars (uses existing UNSPLASH_ACCESS_KEY). NO migrations (LandingPageSection.content is a JSONB-style record; new optional fields are forward-compatible). Backend redeploy required. Pairs with v1.40.2 onError defenses + v1.40.3 broken-fallback removal + v1.40.4 content_filter relaxation. Belt + suspenders + production approval.",
14
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.40.4: UNSPLASH HIT-RATE FIX — content_filter relaxed + 0-result retry. Diagnostic test (scripts/test-unsplash.mjs, run before re-creating a workspace) surfaced two real causes of the broken-hero pattern that v1.40.3's gradient-fallback was masking. (1) content_filter=high was blocking legit medspa imagery. 'minimalist medspa treatment room' returned ZERO Unsplash results with content_filter=high; 'serene aesthetic clinic marble' returned 15. The high filter is over-paranoid for professional stock photography (no nudity/violence required to pass). v1.40.4 drops to content_filter=low across both resolveHeroImageUrlForQuery + resolveGalleryImageUrlsForQueries — same safety profile (Unsplash editorial standards still apply), much higher hit rate. (2) Some LLM-generated queries genuinely return 0 results because the leading adjective is too specific. 'minimalist medspa' → 0; 'medspa' → many. v1.40.4 introduces a broadenQuery() helper that drops the first word and retries once if the original returned 0 results. Common-case fix that raises per-query hit rate from ~80% to ~99% without expanding the API budget meaningfully (only fires on misses). Both resolvers now (a) try the original query, (b) on 0-result OR API-error, retry with the broadened query, (c) on second miss, return empty/skip the slot (gradient empty-state still kicks in as last resort, per v1.40.3 contract). Refactored the request-shape into a shared searchUnsplash(query, apiKey, opts) helper so future tweaks land in one place. SECONDARY OBSERVATION: the diagnostic also surfaced that the production Unsplash app is on DEMO TIER (50 req/hour). Each workspace burns 7 image calls; the 50-req cap → ~7 workspaces/hour ceiling. For sustained testing the operator should submit for production approval at https://unsplash.com/oauth/applications (free, ~24h turnaround) which raises the cap to 5000/hour. v1.40.4 doesn't fix this — only Unsplash's review process can — but the WARNING is logged in the test script output. NO new env vars (uses existing UNSPLASH_ACCESS_KEY). NO migrations. Backend redeploy required. EXPECTED IMPACT: a fresh medspa workspace's 7 image queries (1 hero + 6 gallery) now have ~99% hit rate instead of ~80%, so the rendered page shows real photos in every slot rather than gradient fallbacks for the 1-2 queries that randomly missed pre-1.40.4. Pairs with v1.40.3 onError + gradient defenses for true belt-AND-suspenders coverage.",
15
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.40.3: HOTFIX — kill the broken keyless source.unsplash.com fallback. The HERO Aesthetic Co. test reproduced a hero broken-image icon despite v1.40.2's onError handler being deployed (proven by the gallery rendering 4 medspa photos correctly). ROOT CAUSE: resolveHeroImageUrlForQuery + resolveGalleryImageUrlsForQueries fell back to https://source.unsplash.com/{w}x{h}/?{query} when the official Unsplash API returned no results or errored. That endpoint is DEPRECATED and now frequently returns broken responses, which were stored verbatim in landingPages.sections (LandingPageSection[]) and rendered as broken-image icons before the v1.40.2 onError handler eventually flipped state. The bandage was working but the sutures still leaked. FIX: never store a known-broken URL. Both resolvers now return EMPTY for misses (resolveHeroImageUrlForQuery returns '', resolveGalleryImageUrlsForQueries skips the slot entirely). When hero query misses, hero component renders its designed branded-gradient empty-state from frame zero — no broken icon ever flickers in. When gallery query misses, the masonry grid auto-reflows to a clean N-tile composition (a 6-service gallery with 4 valid Unsplash hits renders as 4 tiles, not 4 photos + 2 broken). Trade-off: workspaces without UNSPLASH_ACCESS_KEY env var now ship gradient hero + empty gallery instead of keyless-CDN attempts. That's the right trade — gradient is intentional; broken icon is broken. Operators get full control by setting UNSPLASH_ACCESS_KEY (free Unsplash developer account, 50 req/hour) or replacing images post-launch via update_landing_section. Server-side: also added an unsplash_gallery_no_results log event to surface API quota exhaustion in production. NO new env vars, NO migrations. Backend redeploy required. EXPECTED IMPACT: HERO Aesthetic Co. (or any fresh medspa) renders a real Unsplash medspa photo in the hero (when API key configured) OR a polished branded-gradient fallback (when not) — never a broken-image icon. Pairs with v1.40.2's per-tile onError defense as belt + suspenders.",
16
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.40.2: HOTFIX — booking timezone bug + foolproof image fallbacks. Three real bugs surfaced by the Vesper Aesthetic test. (1) BOOKING TIMEZONE BUG — slot_outside_business_hours rejection on every booking from a non-UTC customer. ROOT CAUSE: listPublicBookingSlotsAction generated slot strings using server-local time (UTC on Vercel) but workspace business hours are in operator's IANA TZ (America/Chicago for Vesper). Customer in Toronto picked '2:00 PM' → slot string '2026-05-14T14:00' (no Z) → server interpreted as 14:00 UTC = 9 AM CDT = OUTSIDE Vesper's 10 AM–8 PM CDT hours → REJECTED. The submit handler validated correctly in workspace TZ; the slot generator did not. FIX: NEW utcMomentForLocalTime(year, month, day, hour, minute, timeZone) helper builds a UTC Date that, when formatted in target TZ, shows the intended local Y-M-D H:M (DST-robust via Intl.DateTimeFormat round-trip). listPublicBookingSlotsAction now (a) reads workspace.timezone from organizations.timezone, (b) parses the requested date in workspace TZ, (c) builds slot starts via utcMomentForLocalTime so they match workspace's local hours, (d) returns slots as full UTC ISO strings (with Z suffix) — unambiguous, no more browser-vs-server interpretation drift. PublicBookingForm now formats slots via .toLocaleTimeString({timeZone: workspaceTimezone}), shows '< times shown in CDT >' label, and surfaces the workspace's IANA TZ in the meta row. Customer sees operator's actual hours, server sees the same UTC moment, submit validates against the same workspace TZ. (2) HERO IMAGE onError CLIENT-SIDE FALLBACK. Pre-1.40.2 a 404 / unreachable Unsplash URL still rendered as a broken <img> with alt-text visible (Vesper test: 'Board-Certified Skin Care' alt text bled through the gradient overlay). v1.40.2 adds useState imageFailed + onError handlers across all 4 hero variants (cinematic-fullbleed, split-screen-50-50, left-aligned-asymmetric, founder-portrait) — when the image fails, the variant flips to its branded gradient empty-state with the headline's first word as a typographic anchor. Same pattern, robust against any Unsplash hiccup. Hero made 'use client' to support state. (3) GALLERY PER-TILE onError FALLBACK. Pre-1.40.2 broken gallery photos rendered as broken-image icons inside the masonry grid (Vesper had 2 of 6 broken). NEW GalleryTile internal component with useState failed → returns null when image fails. Grid auto-reflows around missing tiles, leaving a clean 4-tile or 5-tile composition instead of broken slots. Project-gallery component made 'use client' to support per-tile state. NO new env vars. NO migrations. Backend redeploy required. EXPECTED IMPACT: bookings in any customer TZ submit successfully. Hero with broken Unsplash URL falls back to a polished branded gradient — operator-perceived 'always intentional' on first generation. Gallery never shows broken-image icons; grid reflows. Vesper booking flow works end-to-end with intake fields collected and emailed via Resend.",
17
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.40.1: VERTICAL-AWARE BOOKING + HERO POLISH. Five fixes after the v1.40.0 Lumen test surfaced the gaps. (1) FOOTER 'Powered by SeldonFrame' BADGE REWRITE. Pre-1.40.1 the badge loaded an SVG file whose text was rendered via SVG <text> with a serif font fallback chain — when the user's browser fell back from 'Instrument Serif' to a wider serif, the text overflowed the SVG viewBox and clipped mid-letter ('SeldonFra' instead of 'SeldonFrame'). Replaced with an inline-component approach: geometric mark as inline SVG (no font dependency, can never clip), wordmark as plain HTML text alongside. Faster paint, robust across all browsers. (2) HERO CINEMATIC-FULLBLEED FALLBACK + LIGHTER OVERLAY. When Unsplash returns a 404 / unreachable URL for the hero query (which happened on Lumen — alt text was visible in the upper-left), the variant now renders a brand-tinted gradient anchor with the headline's first word as a typographic element instead of a broken <img>. Looks intentional, not broken. Also softened the gradient overlay from from-black/30 via-black/50 to-black/80 to from-black/10 via-black/40 to-black/65 so working hero photos actually show through. (3) ARCHETYPE-AWARE HERO QUERY GUIDANCE. enhance-blocks prompt extended with per-archetype Unsplash query examples (cinematic-aspirational → 'minimalist spa treatment room', 'serene aesthetic clinic marble'; clinical-trust → 'modern dental clinic interior bright'; soft-residential → 'tidy living room natural light'; brutalist → 'concrete loft natural light'; etc). The previous universal guidance worked for trades but produced weak medspa hero queries. (4) SERVICE PRE-FILL VIA URL PARAM. Services-grid CTAs now append ?service=<slug> derived from each service name. PublicBookingForm reads useSearchParams().get('service'), shows a 'Service requested' banner at the top of step 3, and includes the requested_service value in the intake responses sent to the operator's CRM. The customer's intent finally reaches the operator without a follow-up call. (5) VERTICAL-AWARE BOOKING FORM FIELDS — the structural ship. NEW src/lib/workspace/booking-intake-fields.ts with per-archetype field schemas. bold-urgency (trades) asks address+phone+issue_type+urgency. cinematic-aspirational (medspa) asks phone+primary_goal+previous_treatments+allergies+preferred_provider. clinical-trust (legal/dental) asks phone+client_status+concern+insurance+referral_source. technical-restrained (B2B) asks company+role+team_size+scope+timeline+budget. soft-residential (home cleaning/landscape) asks address+phone+frequency+preferred_day+special_notes. editorial-warm (craft trades) asks address+phone+scope+timeline+budget. brutalist (creative studios) asks company+project_type+brief+timeline+budget. Plus universal 'notes' textarea trailer for every archetype. NEW BookingIntakeField type in lib/bookings/actions.ts (text/textarea/tel/select/radio with options + helpText + required). When create_full_workspace runs, the orchestrator UPDATEs every booking template's metadata.intakeFields with the archetype's defaults. getPublicBookingContext exposes them. PublicBookingForm renders them dynamically as a custom form (replaced CustomerActionForm for step 3) — input/select/radio/textarea per field type, validation for required fields, all values flow through submitPublicBookingAction's new intakeResponses param into the booking row's metadata.intakeResponses field. Operators see structured lead data ('Address: 1234 Main St | Issue: Active emergency | Urgency: Today') the moment a booking lands in their CRM, not after a follow-up call. NO new env vars. NO migrations (additive metadata). Backend redeploy required. EXPECTED IMPACT: a fresh medspa workspace's booking form now asks 'What's your primary goal for this visit?' + 'Previous treatments?' + 'Allergies or sensitivities?' + 'Preferred provider?' instead of a generic notes field. A fresh roofing workspace asks 'Service address' + 'What's happening?' (with options like 'Active emergency', 'Diagnostic visit', 'Repair quote') + 'How urgent is this?'. A fresh law firm asks 'Are you a new client?' + 'What's the matter?' + 'Insurance carrier' + 'How did you hear about us?'. Each operator's CRM lights up with the EXACT data they need to dispatch / schedule / route the lead, automatically, on first booking. Hero image broken? Branded gradient looks intentional. Footer 'Powered by SeldonFrame' renders cleanly across all browsers. The launch-video moment.",
18
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.40.0: TASTE + CONVERSION + ARCHETYPE SYNTHESIS — the structural ship that closes the gap between SF auto-generated workspaces and hand-tuned designer output. Three forces converge in one commit. (1) AESTHETIC ARCHETYPES (the 'fat skill', inspired by github.com/leonxlnx/taste-skill). NEW src/lib/workspace/aesthetic-archetypes.ts with 7 curated archetypes (editorial-warm / bold-urgency / clinical-trust / cinematic-aspirational / technical-restrained / soft-residential / brutalist), each defining palette + fonts + dials (DESIGN_VARIANCE / MOTION_INTENSITY / VISUAL_DENSITY) + heroVariant + bannedHere tokens + voice (tone / pace / lean-into / avoid words). NOT keyed by vertical — keyed by EMOTIONAL CATEGORY because vertical is too narrow (a custom-restoration roofer has different brand needs than a storm-chaser; both are 'roofing'). NEW classifyArchetype() picks based on (vertical + emergency_service + voice signals + business description keywords). Adding new archetypes = one entry. (2) TASTE-SKILL DISCIPLINE injected into the LLM prompt. enhance-blocks now renders an archetype-specific design brief at the top of every Opus call: voice tone, banned tokens (Inter banned, centered hero banned, 3-equal-card grids banned, AI purple/blue banned, pure black banned), universal taste-skill rules (max 1 accent < 80% sat, transform/opacity-only animation, asymmetric layouts), output discipline (headlines convey value standalone, 'so that' chaining, specific time numbers, effort-reduction subheads). Anti-AI-slop discipline becomes a hard constraint, not a passing instruction. (3) HORMOZI VALUE EQUATION baked into prompt. Above-the-fold gets 80-90% of generation weight per Hormozi: dream outcome in headline, 'so that' principle, time-delay numbers, effort-reduction in subhead, perceived-likelihood-of-success via proof + testimonials, risk-reversal via badges under CTA. NEW hero.tsx renders 4 variants by archetype: cinematic-fullbleed (image as page background, copy overlays), founder-portrait (square portrait right + copy left), split-screen-50-50 (classic 50/50, urgency-friendly), left-aligned-asymmetric (60/40 editorial — the default). NEW ProofTile component above CTA cluster (rating + count + descriptor in compact pill row). NEW RiskReversalBadges row under CTA with license #s, BBB, insured, certifications. (4) FONTS. DEFAULT_ORG_THEME flipped from Inter to Geist. OrgTheme.fontFamily union expanded with Cabinet Grotesk, Satoshi (added Fontshare CDN support to googleFontUrl helper). The taste-skill explicitly bans Inter as the AI-default font; we comply. Each archetype overrides the org theme with its own font pair on workspace creation, so a roofer gets Outfit/Geist, a medspa gets Cabinet Grotesk/Satoshi, a legal firm gets Cabinet Grotesk/Geist. (5) THEME PROPAGATION. After classifying archetype, enhanceLandingForWorkspace UPDATEs organizations.theme with the archetype's primary/secondary/font tokens so PublicThemeProvider cascades the right palette + font on every public surface (landing, booking, intake) automatically. (6) personality_vertical threaded from createFullWorkspace into enhance-blocks so classifier uses the resolved CRMPersonality vertical instead of inferring from soup. SOFT-FAIL on every step. NO new env vars. NO migrations. Backend redeploy required. EXPECTED IMPACT: a fresh roofing workspace renders with Outfit/Geist typography, deep-terracotta + warm-cream palette, left-aligned-asymmetric hero (NOT centered), proof tile above CTA, risk-reversal badges under CTA, taste-skill-disciplined section copy. A fresh medspa renders Cabinet Grotesk/Satoshi, muted gold + cream, cinematic-fullbleed hero. A fresh legal firm renders Cabinet Grotesk/Geist, deep navy + burgundy, restrained left-aligned hero. Same template, same pipeline, FUNDAMENTALLY different visual identities per niche. 9.5+/10 first-shot for the visual surface. Inspired by Trevor Foyer's manual workflow (inspiration → Stitch → design.md → Claude + taste-skill → 21st.dev), automated server-side in ~30s instead of 25min.",
19
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.39.0: IMAGERY + ICON QUALITY PASS — five fixes that close the visible gap between SF auto-generated workspaces and a hand-tuned reference like dfwroofingpro.com. Pre-1.39.0, generation quality was at the $10k bar (Hormozi headline + verbatim testimonials + light mode + distinct service icons all rendered correctly in v1.38.5), but the Bluebonnet Roofing test surfaced four image/icon polish bugs: hero showed Austin city skyline instead of a residential roof, project gallery had random-feeling stock photos, the About section showed an empty 'Feature image' placeholder, and the 'What Sets Apart' benefits cards all rendered the same Sparkles icon. v1.39.0 fixes all of them. WHAT SHIPS: (1) HERO QUERY TIGHTENING. enhance-blocks prompt now teaches Claude to generate hero queries that contain a craft-specific physical noun + composition hint and AVOID leading with city names. 'asphalt shingle residential roof close-up' instead of 'austin storm roofing'. Pre-1.39.0 the city-led queries returned downtown skyline shots because Unsplash interpreted 'Austin' as the dominant subject. (2) SCENERY REJECTION in resolveHeroImageUrlForQuery. Defense-in-depth — even with a tightened prompt, an occasional query slips through and returns scenery. v1.39.0 scans the top 15 Unsplash results and skips ones whose description matches /(skyline|cityscape|aerial view of city|downtown|panorama|landscape|sunset over|view from|tourism)/. Falls back to first result if all match (a hero is better than no hero). per_page bumped from 10 → 15 for more rejection alternatives. (3) GALLERY QUERY GUIDANCE. enhance-blocks prompt extended with explicit GOOD vs BAD query examples per vertical (roofing, plumbing, HVAC). Each query now MUST contain a craft-specific physical noun, MUST NOT lead with a city, and MUST vary across the 6 (no repeated nouns). 'roofer installing shingles residential' instead of 'austin roofing'. (4) BENEFITS DYNAMIC ICONS. Pre-1.39.0 benefits.tsx had a 5-entry PascalCase iconByName map ({Sparkles, Star, Rocket, ShieldCheck, CircleCheckBig}) that didn't match the kebab-case names ('shield-check', 'badge-check', 'wrench') the LLM generates per the prompt. Result: every benefit card rendered Sparkles. Same root cause as v1.38.5's services-grid bug, just in a different component we hadn't touched. v1.39.0 extracts the 60+ entry icon map + normalized resolver to NEW components/landing/sections/icon-resolver.ts and refactors BOTH services-grid + benefits to import from it. Adds ~10 more aliases (trust, trusted, insured, licensed, bonded, family, familyowned, local, experience, experienced) so common LLM benefit-icon picks resolve. enhance-blocks prompt also explicitly tells Claude that benefits.icon must be DISTINCT across the 3 cards. (5) ABOUT SECTION CONDITIONAL IMAGE. features.tsx (used as the about-style block by enhance-blocks) now hides the entire image column when no image is set, instead of showing the 'Feature image' placeholder text — which read as broken/template-y on every workspace whose About we filled with body text but no image asset. When image is absent, renders single-column centered with headline + features pills. When image present, renders the original two-column grid (now using raw <img> with referrerPolicy=no-referrer, same pattern as hero + gallery). NO new env vars. NO migrations. Backend redeploy required. EXPECTED IMPACT: re-test of Bluebonnet (or fresh business) renders a real residential-roofing hero photo (close-up of shingles, roofer working, etc.) instead of city scenery; project gallery shows visibly different work-context photos; benefits cards show 3 distinct icons; About section is clean (no empty placeholder). 9.5/10 first-shot for the visual surface. Booking-form personalization (asking address/phone/issue per vertical) remains for v1.39.x.",
20
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.38.5: THREE PRESENTATION-LAYER FIXES discovered after the Texas MAGA Roofing test (v1.38.4 generation quality was at the $10k bar — Hormozi headline + per-business gallery + verbatim testimonials all rendered correctly — but three smaller bugs kept the page from being light-mode + properly iconed). (1) DEFAULT_ORG_THEME.mode flipped from 'dark' to 'light'. Customer-facing public surfaces (workspace landing, booking page, intake form) should be light by industry convention; operators who want a dark public brand can opt-in via theme settings. The 95% case wants light. Existing workspaces unaffected (their theme is already stored in organizations.theme); only newly-created workspaces inherit the new default. (2) /s/[orgSlug]/[...slug]/page.tsx now applies the same light-mode override v1.38.4 added to /l/ and /book/. proxy.ts rewrites the workspace subdomain root '/' to '/s/[slug]/[defaultLandingSlug]' — NOT /l/ as v1.38.4 assumed. So v1.38.4's landing-page light-mode fix never reached the workspace home; the page rendered against whatever theme.mode was stored. v1.38.5 adds publicTheme = {...theme, mode:'light'} + className='light' wrapper to /s/ so the workspace home is now light by default for both new (light by default per fix 1) and old (already-stored-as-dark) workspaces. (3) PER-SERVICE ICONS — services-grid was rendering <Sparkles> on every card even after v1.38.4 added the dynamic icon resolver, because the enhance-blocks prompt didn't ask Claude for an `icon` field per service AND payloadToSections didn't pass icon through. v1.38.5 adds (a) `icon` to the servicesGrid.services[] schema in the prompt, (b) a 'Per-service icons' instruction block teaching Claude which lucide names + vertical-aliases to pick (storm→cloud-rain-wind, shingle→home, gutter→droplets, drain→droplets, heater→zap, ductwork→home, treatment→leaf, etc.), and (c) icon propagation in payloadToSections.servicesGrid. Extended the ICON_MAP in services-grid.tsx with 24 more vertical aliases (roofing: shingle/metal/gutter/tarp/hail/roof; plumbing: drain/leak/heater/pipe/water; hvac: cooling/ac/heating/furnace/ductwork/duct/thermostat/hvac; spa: treatment/facial/massage/laser; auto: vehicle/van/fleet) so any LLM-generated icon name resolves to a sensible visual. NO new env vars. NO migrations. Backend redeploy required. EXPECTED IMPACT: Texas MAGA Roofing re-test (or fresh workspace) renders LIGHT MODE landing page + LIGHT MODE booking calendar + DISTINCT icons across the 7 service cards. 9.5/10 first-shot for the visual surface. Booking-form personalization (asking address/phone/issue per vertical) remains for v1.39.0.",
21
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.38.4: VISUAL-POLISH BUNDLE — fixes the four issues that kept Hill Mountain Roofing from being 10/10. Generation quality was already at the $10k bar (Hormozi headline + per-business gallery + verbatim testimonials all rendered correctly in v1.38.3); this ship fixes the presentation layer. (1) LIGHT-MODE-BY-DEFAULT for customer-facing surfaces. The pre-1.38.4 PublicThemeProvider only set --sf-* CSS variables; layout components increasingly use Tailwind utility classes (bg-card, text-foreground, bg-muted/15) which resolve to --card / --foreground / --muted-foreground controlled GLOBALLY by the .dark class on <html>. So flipping our theme.mode = 'light' updated --sf-bg to white but bg-card stayed dark, leaving half the page rendered against the global dark cascade. v1.38.4 adds a className='light' wrapper on /l/[orgSlug]/[slug]/page.tsx + book/[orgSlug]/[bookingSlug]/page.tsx to locally disable the global .dark cascade, AND extends themeToCSS to also set the shadcn-convention vars (--background, --foreground, --card, --card-foreground, --muted, --muted-foreground, --border, --input, --ring, --popover, --popover-foreground, --accent, --accent-foreground, --primary, --primary-foreground, --secondary, --secondary-foreground, --destructive, --destructive-foreground) so Tailwind utilities resolve to our intended palette. Two layers of defense — both required because Tailwind v4's :where() selectors otherwise win against inline-styled vars. Same fix kills booking page dark mode (the v1.36.3 bug we'd been failing to root-cause). (2) HERO IMAGE NOW RENDERS. Pre-1.38.4 hero.tsx used next/image, which silently fails when the source domain isn't in next.config.js images.remotePatterns. Hill Mountain Roofing's hero showed alt-text instead of the Unsplash photo because images.unsplash.com wasn't allowlisted. v1.38.4 (a) switches hero to raw <img> with referrerPolicy='no-referrer' + loading='eager' (same pattern project-gallery.tsx already used), AND (b) adds images.remotePatterns for images.unsplash.com / source.unsplash.com / *.public.blob.vercel-storage.com / *.app.seldonframe.com as defense-in-depth for any future next/image usage. (3) DYNAMIC SERVICE ICONS. Pre-1.38.4 services-grid.tsx hardcoded <Sparkles> on every card — even when the LLM generated distinct icon names per service, the component ignored them. v1.38.4 adds a 35-entry name→lucide-component ICON_MAP plus aliases (storm→CloudRainWind, repair→Wrench, install→Hammer, emergency→Zap, warranty→BadgeCheck, etc.) and a normalized-lookup resolver that handles 'BadgeCheck' / 'badge-check' / 'badge_check' equivalently. Each service card now picks its own icon based on the LLM's icon string. Falls back to <Sparkles> on unknown names so a stale icon string never breaks the render. (4) Defense-in-depth on PublicThemeProvider already covered above. NO new env vars. NO migrations. Backend redeploy required. EXPECTED IMPACT: Hill Mountain Roofing test reproduced with v1.38.4 should render light-mode landing + booking, real Unsplash hero photo in the right column, distinct icons across the 7 service cards, and a clearly-light booking calendar where future dates are visually distinguishable from past dates. 9.5/10 first-shot. Remaining gap to 10/10 = operator-supplied logo + real photos.",
22
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.38.3 (bundles 1.38.1 + 1.38.2 + 1.38.3): three quality-bar blocks shipped together. (1.38.1) NEW projectGallery block — masonry grid of 6 stock photos auto-fetched per service via Unsplash from queries Claude generates inside enhance-blocks. Closes the 'feels populated' gap that's the single biggest visible difference between a generic SF workspace and a real-business landing page. NEW src/components/landing/sections/project-gallery.tsx (responsive 2/3/4 col, hover-lift + zoom interaction, optional caption-on-hover, optional bottom CTA). NEW src/lib/crm/personality-images.ts → resolveGalleryImageUrlsForQueries() helper — square 800x800 thumbs via Unsplash API (when UNSPLASH_ACCESS_KEY env set) with deduplication across queries (avoids 6 services all returning the same generic photo) + keyless source.unsplash.com fallback with per-query random seed. enhance-blocks prompt extended with 'Gallery queries' section that teaches Claude to generate 6 vertical-specific Unsplash queries (e.g. for HVAC: 'hvac technician outdoor unit', 'ductwork installation', 'thermostat residential' — bias toward queries that return DIFFERENT photos). projectGallery section emitted between process and serviceArea in the LandingPageSection[] order. (1.38.2) NEW stickyMobileCTA block — fixed bottom-of-screen Call/Book bar, MOBILE ONLY (md:hidden via Tailwind). Industry-standard for trades sites with measured 2-3x mobile booking lift (Cal.com, Calendly, Mr. Rooter, Roto-Rooter precedent). NEW src/components/landing/sections/sticky-mobile-cta.tsx — 56px tap targets meet WCAG 2.5.5, position:fixed bottom:0 with safe-area-inset-bottom for iOS home indicator clearance, z-index:50 sits above sections but below modals, gradient-divided two-column layout (Call left, Book right). Auto-emitted as the LAST section whenever input.phone is set; position:fixed pulls it out of document flow at runtime so visual order doesn't matter. (1.38.3) Testimonial synthesis from Maps reviews paste. NEW testimonials field on create_workspace_from_google_paste MCP tool input — array of { quote, name?, role?, company?, rating? } extracted VERBATIM by Claude Code from review excerpts in the paste. NEVER fabricated — when paste has no review text, the testimonials field is OMITTED entirely (better empty than fake). Backend route /api/v1/workspaces/create-full reads + shape-validates via readTestimonials() (cap 8 entries, quote ≤800 chars, drop entries missing quote). Plumbed through CreateFullWorkspaceInput → createAnonymousWorkspace → soul + enhance-blocks. enhance-blocks emits a testimonials LandingPageSection (between serviceArea and faq in the order) when input.testimonials.length > 0. Quotes pass through verbatim — the LLM does not get to rewrite them. ALL THREE block types registered in src/components/landing/block-registry.tsx with grapesContent placeholders for the visual editor. EMAIL-FIRST GUARDRAIL UNCHANGED. NO new env vars (uses existing UNSPLASH_ACCESS_KEY for higher-quality gallery photos when set; works keylessly via source.unsplash.com when not). NO migrations. Backend redeploy required. EXPECTED IMPACT: every fresh paste-driven workspace now ships with 11 sections including a populated photo gallery + mobile sticky CTA + real testimonials when paste has reviews. Visible-quality bar: 9/10 first-shot. Remaining gap to 10/10 = operator-supplied logos + real photos.",
23
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.38.0: ATOMIC HORMOZI-QUALITY OUTPUT for every workspace. Pre-1.38.0, create_full_workspace produced workspaces whose landing rendered via the BLUEPRINT path (lib/page-schema/seed-landing-from-soul → renderGeneralServiceV1 → contentHtml/contentCss seeded from canned personality.content_templates.hero_headlines[0] — generic 'Welcome to X' copy, generic personality-bundle hero photo, no motion). Tirionforge HVAC happened to look great because Claude Code followed up with the BLOCK-AS-SKILL flow per block (get_block_skill + persist_block); atlantic-plumbing didn't, so it got the canned output. Same atomic create, very different visible quality. Operators NEVER see this dance — they just see the page. v1.38.0 closes the gap atomically: ONE server-side Claude Opus 4.7 call inside the orchestrator generates Hormozi-quality content for hero + servicesGrid + about + benefits + process + faq + cta using the exact same SKILL.md files in src/blocks/*/SKILL.md that Claude Code reads via get_block_skill. Single source of truth, no duplication — the SKILL.md IS the fat skill, the MCP server stays a thin shim. WHAT SHIPS: (1) NEW src/lib/workspace/enhance-blocks.ts. enhanceLandingForWorkspace() loads SKILL.md files, builds prompt with business context (name/city/state/phone/services/description/reviews/emergency/hours/service_area), calls Anthropic via getAIClient (BYOK first via organizations.integrations.anthropic.apiKey, falls back to platform ANTHROPIC_API_KEY env). Model fallback chain: claude-opus-4-7 → claude-sonnet-4-5-20250929. Parses JSON, validates each section, resolves Unsplash for hero.heroImage_query, writes LandingPageSection[] to landingPages.sections, NULLs contentHtml/Css. (2) NEW Step 12.7 inside createFullWorkspace orchestrator (lib/workspace/create-full.ts). Soft-fails on every error path — workspace stays valid (canned-copy Path A intact) if Claude is unreachable. (3) Route /l/[orgSlug]/[slug] preference unchanged but now falls through to PageRenderer correctly because contentHtml/Css are nulled — PageRenderer wraps below-fold sections in <RevealOnScroll> for scroll-triggered fade-up motion. (4) FIRST_CALL_BANNER bumped to v1.38.0 with Hormozi-quality guidance. EMAIL-FIRST GUARDRAIL UNCHANGED: create_full_workspace + create_workspace_from_google_paste still return BEFORE the email collection step — operator can't see URLs until they reply with email, finalize_workspace mints magic admin link + sends welcome email via Resend. NO new env vars (uses existing ANTHROPIC_API_KEY + organizations.integrations BYOK). NO migrations. Backend redeploy required. EXPECTED IMPACT: every fresh workspace produced by either atomic-create tool looks $10k-tier first-shot — Hormozi value-equation hero copy + per-business Unsplash photo + scroll-triggered motion + branded services grid with prices + 3-step process + real FAQs + final CTA. No 'create workspace then enhance blocks' two-step dance. atlantic-plumbing.app.seldonframe.com on next workspace re-creation will render with the rich output by default. Cost ~$0.10 per workspace via BYOK or platform key — irrelevant given the operator-visible quality uplift.",
24
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.37.0: GOOGLE MAPS PASTE → WORKSPACE (no Places API). New MCP tool create_workspace_from_google_paste — sibling to create_full_workspace, same backend pipeline (/workspaces/create-full), same atomic guarantees, same finalize_workspace follow-up. The differentiator is the docstring: it teaches Claude Code how to extract structured fields from a raw Google Maps business listing — bold title → business_name, phone-icon row → phone, location-pin line → address (parse city + state), categories chip row + 'Services' section → services (deduped), '4.7 ★ (950)' → review_rating + review_count, 'Monday: 9 AM-5 PM, Tuesday: closed, …' → weekly_hours in canonical {monday:{enabled,start,end}, ...} shape. Thin harness, fat skill: NO regex parser, NO LLM call, NO Places API key on the SF side. Claude Code (the agent) parses the paste; the MCP server is just a typed shim. WHAT SHIPS: (1) NEW tool create_workspace_from_google_paste in skills/mcp-server/src/tools.js. Inherits create_full_workspace's input schema and adds two fields — weekly_hours (canonical schedule shape) and google_place_url (audit URL). Tool description embeds 11 numbered extraction rules so Claude Code generates the right call shape on first try. (2) BACKEND extends CreateFullWorkspaceInput with weekly_hours + google_place_url. After the orchestrator's existing 13 steps land, two new soft-fail steps run: 12.5 UPDATE every booking template's metadata.availability with the parsed hours (closing the loop with v1.36.4's read-path fix — the booking page now hydrates real Maps-extracted hours on first GET). 12.6 stamp soul.business.maps_url with the source URL for future re-sync. Both UPDATEs catch + log errors; never block workspace creation. (3) ROUTE /api/v1/workspaces/create-full reads + shape-validates weekly_hours via readWeeklyHours() — full-day-name keys only (sunday/monday/.../saturday), HH:MM 24-hour format, malformed entries silently dropped (workspace creation must not fail on a paste-extraction quirk). (4) FIRST_CALL_BANNER updated with paste-workflow guidance: 'when the operator pastes a Google Maps business listing, use create_workspace_from_google_paste — NOT create_full_workspace.' (5) Updated banner header from v1.34.0 to v1.37.0. NO new env vars (no Places API key, no Maps key). NO migrations. Backend redeploy required. EXPECTED IMPACT: when the user says 'make me a workspace from this Google Maps listing for Top Plumbing Experts', Claude Code parses the paste in-context (Maps listings are plain text), calls create_workspace_from_google_paste with the structured fields, and the operator's real business hours appear on the public /book page immediately — no second tool call, no Places API cost, works in any language. ~1 day total ship. Pairs with v1.36.4 fix.",
25
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.36.4: HOTFIX — booking 'No times available' on workspaces created via the MCP create_appointment_type route. Bug: src/app/api/v1/booking/appointment-types/route.ts wrote bookings.metadata.availability with 3-letter day keys (mon/tue/wed/thu/fri/sat/sun), but normalizeAvailability() in lib/bookings/actions.ts reads source[\"monday\"] etc. — full names. Result: every daySource lookup returned undefined → fell back to defaults silently (Mon-Fri 9-5 enabled, weekend disabled). The defaults masked the bug at create time, but any subsequent partial override via the dashboard form (which writes full-name keys) ended up as a mixed shape that didn't hydrate as the operator expected, surfacing as 'No times available' on the public /book page. Atlantic Plumbing dogfood reproduced this. Other writers were already correct: lib/blocks/templates.ts (buildAppointmentAvailabilityFromBlueprint) and lib/page-blocks/persist.ts (weeklyAvailabilityToCanonicalAvailability) explicitly map 3-letter→full names; lib/soul/install.ts writes empty {} which falls back safely. WHAT SHIPS: (1) appointment-types/route.ts now writes monday/tuesday/wednesday/thursday/friday/saturday/sunday — single source of truth is the AvailabilityDayKey union in lib/bookings/actions.ts. (2) DEFENSIVE READ: normalizeAvailability() also accepts legacy 3-letter keys via a shortToFullDayKey map. Reads source[fullKey] first, falls back to source[shortKey] if the full key is undefined. This rescues any prod rows already stored with the broken shape — no DB migration required. NO new env vars. Backend redeploy required. EXPECTED IMPACT: every workspace whose booking template was created via the MCP route — including Atlantic Plumbing — immediately renders Mon-Fri 9-5 slots correctly on next deploy. New workspaces store the canonical full-name shape from now on. Future partial-override writes from the dashboard form interleave cleanly.",
26
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.36.3: HOTFIX — force light mode on customer-facing booking pages. Bug: the default workspace theme.mode is 'dark' (per DEFAULT_ORG_THEME), so themeToCSS sets --sf-bg = #09090b and --sf-text = #fafafa. The v1.36.1 booking page rebuild faithfully used those CSS variables, which meant every fresh workspace's /book page rendered in dark mode. The user reported atlantic-plumbing.app.seldonframe.com/book showing dark-on-dark text that read as 'fucking ugly' next to the polished light-mode reference (hvac.tirionforge.com/book). FIX: book/[orgSlug]/[bookingSlug]/page.tsx now overrides theme.mode = 'light' on the OrgTheme passed to PublicThemeProvider for booking pages. Operators' brand color (primaryColor) and font still cascade unchanged — only mode is flipped. Industry convention: customer-facing booking pages are light by default (Cal.com, Calendly, every booking SaaS). If a workspace explicitly wants a dark booking page, we'll add an opt-in toggle later; light is the right default for the 95% case. NO migrations, NO new env vars. Backend redeploy required. EXPECTED IMPACT: every booking page on every workspace immediately renders in light mode — readable text, clean white background, brand-colored accents — on next deploy.",
27
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.36.2: ENTITLEMENT BYPASS for SF super-admins. Workspaces owned by anyone whose email is in SF_SUPERADMIN_EMAILS skip every tier-based limit assertion (assertLandingPageLimit, assertEmailSendLimit, assertAiCallLimit, assertTeamMemberLimit, assertPortalEnabled, assertAiCustomizationEnabled). Rationale: the SF team itself dogfoods the platform on its own accounts (Maxime owns Cypress HVAC + Atlantic Plumbing + future test workspaces); without this bypass, the second landing page they create hits 'upgrade_required limit:landingPages current:1 tier:starter' and blocks the demo flow. Real customers don't see this — only the SF-internal team. Right move is short-circuit the check rather than have the team manually upgrade test accounts every time. WHAT SHIPS: NEW isOwnerSuperAdmin(orgId) helper in src/lib/tier/limits.ts. Joins organizations.ownerId → users.email, checks against the SF_SUPERADMIN_EMAILS allowlist via existing isSuperAdminUser(email) from @/lib/auth/super-admin. Each of the 6 assert* functions starts with `if (await isOwnerSuperAdmin(orgId)) return;` early-exit. NO changes to limit configuration; NO changes to the /super-admin dashboard's metrics (they still show real plan tiers); NO new env vars (uses the same SF_SUPERADMIN_EMAILS that gates /super-admin route). Backend redeploy required. EXPECTED IMPACT: Maxime can create unlimited landing pages, send unlimited emails, make unlimited AI calls, etc. on his own test workspaces (Cypress HVAC, Atlantic Plumbing, future test orgs) without hitting starter-tier limits. Real customers' workspaces unaffected.",
28
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.36.1: BOOKING PAGE COMPLETE UI REBUILD (tirionforge-quality). Triggered by user feedback: 'why is the calendar page now fucking ugly?' — comparing atlantic-plumbing.app.seldonframe.com/book to a polished industry reference (hvac.tirionforge.com/book). The pre-v1.36.1 booking page was bare: just an unbranded title, an unstyled DayPicker, and a 3-column time-slot grid wrapped in a 'Powered by SeldonFr...' watermark with no business identity, no phone CTA, no step indicator, no service details sidebar. WHAT SHIPS: (1) public-booking-form.tsx COMPLETE JSX REWRITE. State machine extends from 2 steps (pick-time → enter-details) to 3 (pick-date → pick-time → enter-details) so the time-slot view gets its own dedicated step. All action calls preserved (listPublicBookingSlotsAction, submitPublicBookingAction), demo-readonly + demo-blocked-error handling preserved, Stripe checkout redirect preserved, success screen preserved. New shell: full-page main wrapper with workspace background; top header card showing business name + tap-to-call phone CTA pill; body card with two-column layout (38%/62% on desktop). LEFT column: 'Schedule a service' eyebrow + appointment title + description + meta list (duration / on-site / timezone) + 'Booking with' eyebrow + business name. RIGHT column: 3-step indicator (Pick a date · Choose a time · Confirm details) with active/past states + circle numbers/checkmarks; calendar (step 1) / time slot grid (step 2) / details form (step 3). Step indicator allows clicking back to earlier steps. (2) DayPicker BRANDED STYLING via injected CSS string (skips styled-jsx dependency): primary-color selected day, primary-tinted hover, rounded slots, uppercase font-weighted day-of-week headers, 44px cell size for fingertip tap. CSS variables fall back to teal #21a38b when --sf-primary is unset. (3) Time slot grid REDESIGNED: 12px-radius rounded-xl pills with hover-lift micro-interaction, primary-tint background when selected, loading state shows 6 skeleton pulse cards instead of 'Loading available times…' text. (4) Step 3 confirmation card highlights selected date+time in a primary-tinted box with a 'Change' link back to step 2. (5) page.tsx EXTENDED to fetch org name + soul + testMode in one query, parses business phone from soul.business.phone / soul.contact.phone / soul.phone (best-effort across schemas), passes businessName + businessPhone + appointmentName + appointmentDescription to PublicBookingForm. (6) BLUEPRINT-RENDER PATH BYPASSED. The page route used to prefer the calcom-month-v1 blueprint renderer's stored HTML/CSS pair; we're skipping that path so every booking page gets the new React UI immediately, regardless of whether it was scaffolded with the old renderer. The blueprint-renderer module + the bookings.contentHtml/contentCss columns still exist; fixing the renderer's HTML output to match is a separate ship. NO migrations, NO new env vars. Backend redeploy required. EXPECTED IMPACT: every booking page on every workspace immediately gets the rich tirionforge-quality UI — no operator action required, no rerun of any tool. Atlantic Plumbing's /book URL goes from 'plain ugly' to 'looks like a professional business' on the next deploy. v1.36.x deferred: booking weekly_availability hydration ('No times available' on fresh booking pages — even though defaultAvailabilitySchedule() returns Mon-Fri 9-5, something between create-time and render-time is dropping it; needs a deeper trace), entitlement bypass for SF_SUPERADMIN_EMAILS (upgrade_required blocks 2nd landing page on starter tier), slow event emissions, vertical packs.",
29
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.36.0: LANDING-PAGE QUALITY PASS — three new block primitives (servicesGrid, emergencyStrip, serviceArea) + Hero empty-state upgrade + FIRST_CALL_BANNER prompt-fatigue + composition guidance for trades verticals. Triggered by real-user dogfood feedback (Atlantic Plumbing): generated landing page had 3-4 sections, no hero photo, no services grid with prices, no emergency callout. World-class plumbing site has 10-15 sections; gap was 7-10. New blocks: ServicesGrid (per-service cards with price + duration + Book CTA, replaces SaaS-style PricingSection for trades), EmergencyStrip (brand-colored 24/7 callout banner with tap-to-call), ServiceArea (chip cloud of cities/neighborhoods served, no embedded map). Hero block empty state: replaced 'Add hero media' gray box with brand-tinted gradient (primary/15 → primary/5 → background) anchored by huge primary-color first-word typography, looks intentional pre-photo. FIRST_CALL_BANNER guidance: bundle MCP tool calls in parallel via Promise.all, recommend the operator pick 'Yes, and do not ask again' the first time create_landing_page / persist_block / build_website_chatbot fire (caches per-tool autoapprove for the project), default to 10-section composition for local-service businesses (navbar → hero → emergencyStrip → trust → servicesGrid → benefits → process → serviceArea → testimonials → faq → cta → footer), substitute servicesGrid for PricingSection on per-service businesses (trades, salons) vs tier-based businesses (SaaS, coaches). Block-registry wires all three new types with grapesContent placeholders for the visual editor. types.ts extends LandingPageSection union and adds three Content types. NO migrations, NO new env vars. Backend redeploy required. EXPECTED IMPACT: next workspace Claude Code generates for a trades business produces a 10-section landing page with prices + emergency CTA + service area + tasteful hero gradient — not the 3-4-section sparse page from the Atlantic Plumbing dogfood. v1.36.x DEFERRED: booking weekly_availability hydration ('No times available' on fresh booking pages), bypass entitlements for SF_SUPERADMIN_EMAILS (upgrade_required error blocks 2nd landing page on starter tier), slow event emissions (10s elapsed on contact.created/intake.submitted), form schema field-type drops, vertical packs.",
30
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.35.7: HOTFIX — logout was broken when sf_admin_token cookie present. Bug: proxy.ts line 196 treats `isAuthenticated = hasNextAuth || hasAdminToken`, where hasAdminToken checks the sf_admin_token cookie (set during Claude Code MCP onboarding for admin-token sessions, format wst_*). Pre-v1.35.7, signOutAllSessionsAction cleared NextAuth + sf_operator_session but NOT sf_admin_token. Users who had ever onboarded via the MCP admin-token flow (e.g., maximehoule100@gmail.com after testing the MCP install for Cypress HVAC) had a surviving sf_admin_token cookie. Clicking 'Log out' would clear NextAuth + operator session, redirect to /login, but proxy.ts saw the admin-token cookie as still-authenticated and bounced /login → /dashboard. Symptom: clicking Log out appears to do nothing. WHAT SHIPS: signOutAllSessionsAction now clears all three cookies (sf_operator_session, sf_admin_token, NextAuth session) in one call. Common cookieClearOpts hoisted to a const for clarity. The wst_* token IS NOT revoked server-side here — it's still valid for MCP callers using it as a Bearer header. We only clear the COOKIE so this browser session stops being 'authenticated.' Server-side token revocation requires a separate flow (delete api_keys row); not the same operation as sign-out. NO migrations, NO new env vars. Backend redeploy required. EXPECTED IMPACT: clicking 'Log out' from the operator dashboard now actually logs you out, regardless of how many auth sessions accumulated. /login renders cleanly. You can sign in again as a different user, or test the SF Admin flow without leftover state.",
31
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.35.6: HOTFIX — operator-dashboard sidebar surfaces SF Admin entry for super-admins. UX bug from the v1.35.x series: when Maxime (whose email is BOTH the SF_SUPERADMIN_EMAILS allowlist email AND the owner of the Cypress HVAC operator workspace) logs in, he lands on the operator dashboard. The operator sidebar has no nav item pointing at /super-admin, so he can't navigate to the platform-admin surface without typing the URL manually. Same problem will hit any future SF team member who's also an operator on the platform. WHAT SHIPS: (1) Sidebar prop signature extends with isSuperAdmin?: boolean. When true (and isOperatorSession is false), the SYSTEM nav group renders an extra 'SF Admin' entry pointing at /super-admin (uses Shield icon from lucide). (2) sidebar-nav.tsx iconMap extended with shield: Shield so the new entry resolves correctly. (3) (dashboard)/layout.tsx imports isSuperAdminUser from @/lib/auth/super-admin and resolves isSuperAdmin = !isOperatorSession && await isSuperAdminUser(user?.email). Operator portal sessions never get the SF Admin entry regardless of email — they're sub-tenants on the platform, not SF staff. (4) Pass-through plumbing: dashboard layout → Sidebar component. NO migrations, NO new env vars (uses existing SF_SUPERADMIN_EMAILS). Backend redeploy required. EXPECTED IMPACT: Maxime logs in to maximehoule100@gmail.com, lands on the Cypress HVAC dashboard as usual, scrolls to the bottom of the sidebar, sees a small 'SF Admin' entry with a shield icon under SYSTEM. One click → /super-admin. Same email, same session, no logout/login dance. Future SF team members who are also operators on the platform get the same affordance automatically.",
32
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.35.5: SUPER-ADMIN HEALTH TAB (final ship in v1.35.x). Final ship in the v1.35.x series. Health tab covers what's available locally without instrumenting Sentry/Datadog: workflow run distribution + recent failures, event volume per day for 7 days, top events, and validator failure rate (the v1.28.6 critical-fail signal). Sentry-backed API error rate + Vercel Analytics p95 latency + LLM provider observed uptime defer to v1.35.5.x. WHAT SHIPS: (1) NEW src/lib/super-admin/health.ts. getHealthMetrics() returns 3 views, all wrapped in unstable_cache (5min TTL, tagged super-admin:health): workflows (status distribution from workflow_runs + total / completed / failed counts + successRate computed only over terminal runs + recentFailed list of 10 most recent failed runs with archetypeId + orgId), events (7-day daily bucket counts via Postgres date_trunc, top 10 events by count, total7d), validators (sample of last 1000 assistant turns from agent_turns, share with any failed validator via jsonb_array_elements unnesting validatorsPassed JSONB, failureRate null when sample <20). Each query graceful-fallback. (2) src/app/super-admin/health/page.tsx upgraded from placeholder. Layout: top KPI strip (3 cards: Workflow success rate ≥95 green, Validator failure rate ≤5 green, Events 7d), Workflow run status distribution table with status badges (completed=teal, failed=red, cancelled=neutral, waiting=amber, running=blue), Recent failed workflows table (run ID + archetype + workspace link), Event volume 7-day bar chart, Top events table. Empty hints when nothing's recorded yet. NO migrations, NO new env vars. Backend redeploy required. EXPECTED IMPACT: Maxime can answer 'is the platform healthy' in <5 seconds. Workflow success rate + validator failure rate are the two best leading indicators for runtime correctness; below thresholds (95% / 5%) the cards turn warn-amber. Final v1.35.x ship completes the super-admin shell — Maxime now has Overview / Users / Workspaces / Agents / Revenue / Health all wired with real data. Future ships add Stripe-sourced revenue (downgrades, churn, cohort retention) and Sentry-sourced health (API error rate, p95 latency, LLM uptime).",
33
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.35.4: SUPER-ADMIN REVENUE TAB (local-DB approximation; Stripe-sourced version in v1.35.4.x). Fifth ship in v1.35.x. Maxime can now see MRR by plan, MRR over time (12-month bar chart), conversion funnel (signup → workspace → agent → paid), and recent paid signups. Local-DB-only for speed (~50ms); Stripe API direct (with proper churn + downgrade modeling) graduates next. WHAT SHIPS: (1) NEW src/lib/super-admin/revenue.ts. getRevenueMetrics() returns 4 views, all wrapped in unstable_cache (5min TTL, tagged super-admin:revenue): byPlan (Free/Growth/Scale headcount + monthly $ contribution + total MRR + paid-user count), monthly (12-month MRR snapshot — at each month-end, count paid users with createdAt <= month-end and multiply by current plan price; explicitly an approximation that doesn't model downgrades or churn), funnel (4 steps: Signed up / Created a workspace / Has at least 1 agent / Upgraded to paid — each with retainedFromPrevious % so drop-offs point at activation work), recentPaid (last 20 users on Growth or Scale ordered by createdAt desc, with link to /super-admin/users/<id>). 'Has at least 1 agent' funnel step uses a CTE counting distinct ownerIds whose orgs have any agent. Graceful fallback (.catch returning zeros) so a single failed query doesn't blank the dashboard. (2) src/app/super-admin/revenue/page.tsx upgraded from placeholder. Layout: top strip (3 cards: MRR with primary teal accent + ARR + Paid users), 'MRR over time' 12-bar chart (gradient teal bars, hover-tooltip via title attr, 0 + max-$ axis labels), 'By plan' table (Plan badge / Users / Monthly $ / % of MRR), 'Conversion funnel' visual (4 horizontal bars showing absolute count + % retained from previous step + bar width relative to first step), 'Recent paid signups' table (User / Plan badge / Joined date — clickable to user detail). (3) FIX in revenue.ts: db.execute() returns a NeonHttpQueryResult not an iterable; original destructuring [const [row] = await db.execute(...)] failed type check. Pattern corrected to const r = await db.execute(...); const row = r.rows?.[0] as Type. NO migrations, NO new env vars. Backend redeploy required. EXPECTED IMPACT: Maxime sees MRR composition (which plan is the engine), the 12-month trend (is the line going up?), and the funnel (where signups die before becoming paid). Stripe-sourced numbers (real MRR with churn/downgrades, failed-payment dashboard, cohort retention) land in a follow-up — for v1.35.4 the local approximation is enough to make decisions.",
34
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.35.3: SUPER-ADMIN AGENTS TAB (fleet health). Fourth ship in v1.35.x. Platform-wide agent metrics: fleet status counts, eval pass rate of live agents, per-archetype health, top failing scenarios, conversation volume. WHAT SHIPS: (1) NEW src/lib/super-admin/agents.ts. getAgentMetrics() returns 5 views, all wrapped in unstable_cache (5min TTL, tagged super-admin:agents): Fleet status (count by status: live / draft+test / paused / total), Per-archetype breakdown (count + recent eval pass rate per archetype using a CTE that ranks evals by ranAt DESC and picks the most recent run per agent×scenario), Recent pass rate (overall % of most-recent eval runs passing across all live agents), Top failing scenarios (scenarios with ≥3 attempts, sorted by fail rate descending, top 10), Conversation volume (24h / 7d / 30d started + completed-without-escalation share for 30d). The CTE uses ROW_NUMBER() OVER (PARTITION BY agent_id, scenario_id ORDER BY ran_at DESC) to handle the most-recent-run-per-pair query in one round trip. Graceful fallback (.catch returning zeros) so a single failed query doesn't blank the dashboard. (2) src/app/super-admin/agents/page.tsx upgraded from placeholder. Layout: 'Fleet status' strip (4 cards: Live / Draft+Test / Paused / Total — Live gets primary teal accent), 'Eval pass rate · live agents' big-number card with progress bar (≥87 green, 75-87 amber, <75 red, includes context line about the 87.5% publish gate), 'Per-archetype health' table (archetype / total / live / pass rate badge with same color coding), 'Top failing scenarios' table (scenario ID / attempts / fails / fail rate badge — fail rate ≥50% gets red, lower amber), 'Conversations' strip (4 cards: 24h / 7d / 30d started + completed-share-30d). Each empty state has an honest 'no data yet' explanation rather than fake numbers. NO migrations, NO new env vars. Backend redeploy required. EXPECTED IMPACT: Maxime can answer the 'is the runtime healthy' question in <5 seconds. The eval pass rate big-number + the top-failing-scenarios table are the two views that drive the next skill-pack improvements — when a specific scenario shows up at 67% fail rate across 50 attempts, that's the next thing to fix in the markdown skill. As more workspaces publish agents, the data gets richer.",
35
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.35.2: SUPER-ADMIN WORKSPACES TAB (list + detail). Third ship in v1.35.x. Now Maxime can see all SF workspaces, sort by activity / created / name, search by name or slug, filter by Soul template, and click into any workspace to see its agents, lifetime tokens, lifetime LLM cost, and activity rollups. WHAT SHIPS: (1) NEW src/lib/super-admin/workspaces.ts. listWorkspaces({ search, soulId, sort, cursor, limit }) returns paginated rows with owner email + last activity + conversations 24h + live agents count. Joins owner via single users-batch query (no N+1). Last activity per org via single MAX(createdAt) GROUP BY against activities table. Conversations 24h via count(agentConversations.id) GROUP BY. Live agents via count(agents) WHERE status='live' GROUP BY. Sort by 'created' uses cursor pagination; 'activity' sorts the current page client-side (post-query); 'name' uses asc(name). Three sort modes; cursor only available on 'created'. getWorkspaceDetail(workspaceId) returns workspace profile + owner email/id + activity 24h/7d/30d buckets + lifetime LLM cost (sum llmCostCents) + lifetime tokens (sum tokensIn + tokensOut) + total conversations + distinct contacts + agents list with status/archetype/channel/createdAt. (2) src/app/super-admin/workspaces/page.tsx upgraded from placeholder to real list. URL-driven filters (search, soul, sort). Table columns: Workspace (name + slug, click to drill), Owner (email), Soul (template badge), Live agents (right-aligned, tabular-nums, dimmed when 0), Convos · 24h, Last activity (relative time). Footer note explains the cursor + sort interaction. (3) NEW src/app/super-admin/workspaces/[workspaceId]/page.tsx — workspace detail. Breadcrumb back to /super-admin/workspaces. Header: workspace name + slug-as-subdomain (mono) + Soul badge + 'owned by [email]' link to /super-admin/users/<id>. Detail card on the right with Workspace ID + Created date + Subdomain. Activity stat strip (4 cards: 24h / 7d / 30d / all-time conversations + distinct contacts subtitle). Lifetime usage row (2 cards: Lifetime tokens + Lifetime LLM cost in $X.XX format with 'self-reported by the runtime · BYOK' subtitle, primary teal accent). Agents section: count + live/draft/paused breakdown header, then table of all agents with status badges (live = teal, paused = amber, draft/test = neutral). Empty state for workspaces with no agents yet. NO migrations, NO new env vars. Backend redeploy required. EXPECTED IMPACT: Maxime can see every workspace, sort by 'most active' to find power users, sort by 'newest first' to see launch-week signups, and click any workspace to see its full health profile (agents, lifetime tokens, recent activity). The two questions that matter at this stage — 'who's actually using SF heavily' and 'are agents being published' — answer in <5 seconds.",
36
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.35.1: SUPER-ADMIN USERS TAB (list + detail). Second ship in the v1.35.x series. v1.35.0 gave Maxime the 4 hero numbers; v1.35.1 lets him drill into who's signing up. WHAT SHIPS: (1) NEW src/lib/super-admin/users.ts. listUsers({ search, plan, cursor, limit }) returns paginated rows + totalForFilter (cached separately by search+plan, 60s TTL, tagged super-admin:users). Search is case-insensitive ILIKE on email + name. Plan filter restricts to a TierId. Cursor pagination on createdAt DESC for stable ordering. Workspace counts joined via single GROUP BY query keyed by ownerId — no N+1. getUserDetail(userId) returns the user + workspaces owned + workspaces joined-as-member (deduped) + last activity per workspace via single MAX(createdAt) GROUP BY query against the activities table. (2) src/app/super-admin/users/page.tsx upgraded from placeholder to real list. Server-rendered. URL-driven search/filter (no client interactivity except the form which submits via GET). Header shows total matching the filter. Search input + plan select dropdown (All / Free / Growth / Scale) + Apply button + Clear link when filters are active. Table columns: User (name + email, click row to drill), Plan (badge — paid plans get teal accent), Workspaces (owned count), Joined (date), Stripe (last 6 chars of customer ID, monospace pill). Pagination: First page + Next page links when applicable. Tabular-nums for the workspace count column. Empty state when no users match. Footer with mechanics ('cursor-based on createdAt DESC'). (3) NEW src/app/super-admin/users/[userId]/page.tsx — user detail. Breadcrumb (Users · email). Header with name (or email if no name set) + email + plan badge + email-verification status (amber when unverified). Detail card on the right with User ID + Joined date + Stripe customer (mono). Two sections: 'Workspaces owned' (count + table of org name/slug/soul/last activity/created) and 'Joined as member' (count + table with membership status replacing the created column). Each workspace row links to /super-admin/workspaces/<id> (lands at v1.35.2's empty page today; the link works regardless). Last activity formatted as relative time ('3h ago', '12d ago') falling back to absolute date for older. Empty hints for the 'no workspaces yet' / 'not a member of any other' cases. NO migrations, NO new env vars. Backend redeploy required. EXPECTED IMPACT: Maxime can now see every signup, search by email, filter by plan, and click any user to see their workspaces. The two questions that matter at this stage of the launch — 'who's actually paying' and 'is this user activating (created a workspace, has recent activity)' — are answerable in <5 seconds.",
37
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.35.0: SUPER-ADMIN DASHBOARD (skeleton + 4 hero numbers above the fold). First ship in the v1.35.x series that gives Maxime full visibility into SF the business — users, paid tiers, workspaces, agent runs, MRR, revenue. Per the first-principles reflection (graceful disclosure / smart defaults), this ship is the smallest cut that gives real signal: 4 numbers + the routes for 5 deeper tabs that come in v1.35.1–v1.35.5. WHAT SHIPS: (1) NEW src/lib/auth/super-admin.ts. requireSuperAdmin() server-side gate that checks SF_SUPERADMIN_EMAILS env var (comma-separated, whitespace-tolerant allowlist). Non-admins redirect to /dashboard with no flash. Why env-allowlist not a users.is_super_admin column: pre-launch the team is one person; over-engineering the permission model adds complexity for zero payoff. Graduates cleanly to a DB column when team grows >3 SF admins. isSuperAdminUser(email) helper for callers that need a check without redirect. (2) NEW src/lib/super-admin/metrics.ts. getHeroMetrics() returns 4 numbers wrapped in unstable_cache with 5-minute TTL: MRR (sum users.planId × plan price across paid tiers — fast local approximation; Stripe-sourced source-of-truth lands in v1.35.4), ARR (MRR × 12), Paid signups · 7d (count users where planId in ('growth','scale') AND createdAt > now() - 7d), Active workspaces · 24h (countDistinct organizations from activities table where createdAt > now() - 24h). Each query is a single SELECT with proper indexes. Tagged super-admin:billing + super-admin:activity for revalidation. Graceful fallback (.catch returning zeros) so a single failed query doesn't blank the whole dashboard. Subtitle on each card adds context (e.g. 'across 43 paid users' under MRR). (3) NEW src/app/super-admin/layout.tsx — Server Component that gates with requireSuperAdmin() before any UI hits the wire. (4) NEW src/app/super-admin/super-admin-sidebar.tsx — client component, dark-theme sidebar mirroring the operator dashboard pattern. 6 nav items (Overview / Users / Workspaces / Agents / Revenue / Health) with lucide-react icons + active highlight via pathname.startsWith. Bottom shows admin email + 'Back to operator dashboard' link. (5) NEW src/app/super-admin/page.tsx — Overview page. 'Today' headline + 4 hero stat cards (the MRR card gets the primary teal-tinted accent; others stay neutral). Computed-at timestamp + cache-TTL footnote. Below: 5 drill-down cards pointing at the deeper tabs (each labeled with the ship version that wires them). (6) NEW src/app/super-admin/placeholder-tab.tsx — shared shell for the 5 tabs that ship empty. Each placeholder has the tab title + ship version + summary + bullet list of what's coming. Honest framing ('Coming in v1.35.x'). (7) NEW src/app/super-admin/{users,workspaces,agents,revenue,health}/page.tsx — 5 placeholder pages composing PlaceholderTab. Routes resolve, sidebar highlight works, no fake data. NO migrations, NO new env vars (SF_SUPERADMIN_EMAILS is operator-managed in Vercel project settings). Backend redeploy required. EXPECTED IMPACT: Maxime can navigate to /super-admin in production and see the 4 numbers that matter for the launch — MRR (critical for investor conversations), paid signups in the last 7 days (critical for launch-week tracking), active workspaces in the last 24h (critical for 'is anyone using it'). Total ship: ~6-8 hours of work in one commit; subsequent v1.35.x ships layer on Users / Workspaces / Agents / Revenue / Health surfaces incrementally without changing the shell.",
38
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.34.4: APPS/ DELETED + AUTH-COOKIE-JAR PURGE + SECURITY.MD UPGRADE. Root cause discovery: the failing 'Production — seldonframe-web' Vercel project (red-X on every deploy in the user's deployments screenshot) was driven by the orphan apps/web/ Next.js 14.2.5 marketing site. The current marketing site lives at packages/crm/src/app/(public)/landing-client.tsx (Next 16). apps/web was a 2-month-old monorepo split that never got cleaned up, with overlapping hero / pricing / problem / intelligence components and stale email-capture/waitlist flows. apps/cloud and apps/pro were stub scaffolds with single page.tsx files. WHAT SHIPS: (1) DELETED apps/ ENTIRELY (apps/web 1900+ LOC stale marketing site + apps/cloud + apps/pro stubs). Removed dev:web and build:web scripts from root package.json. Removed 'apps/*' from pnpm-workspace.yaml. Vercel will stop creating seldonframe-web deploys on next push (the project itself still needs to be deleted/disconnected in Vercel dashboard — this code-side change just removes what was triggering it). (2) DELETED 5 MORE AUTH-COOKIE-JAR FILES: auth-check.txt, auth-flow.txt, auth-live-debug.txt, auth-recheck.txt, auth-state.txt. All Netscape HTTP Cookie File format from curl debugging sessions, same pattern as auth-check-redo.txt deleted in v1.34.3. Each contained stale __Secure-authjs auth tokens — security concern even though expired, plus they shouldn't have been committed. (3) DELETED soul.md (75-line older overview file; SOUL_SPEC.md is the canonical architecture spec). (4) SECURITY.md MAJOR UPGRADE (44 → 90 lines). Restructured around SF's actual attack surfaces. Added 'Safe harbor' section with explicit no-legal-action commitments for good-faith researchers. NEW 'In scope' table with 8 surfaces ranked by criticality: BYOK LLM key handling (AES-256-GCM at rest with ENCRYPTION_KEY env var — decryption side-channel, ENCRYPTION_KEY exposure path, plaintext key in logs would all be reportable), MCP server bearer token leakage / cross-workspace request smuggling, agent runtime + eval gate (eval-gate bypass, prompt injection that bypasses critical-fail validators, regen-loop infinite cost amplification), tenant scoping (workspaceId/orgId IDOR), auth (NextAuth + wst_* bearer tokens), public surfaces (landing-page XSS, embed iframe postMessage), workflow runtime, webhook signature verification. Each row explains why-it-matters + concrete examples-we-want-to-see. NEW 'Out of scope' section with 7 explicit exclusions (upstream-dep CVEs, social engineering, self-XSS, performance-DoS, deprecated code paths in git history). Disclosure process detailed: 24h ack, 7-day triage, same-day patch for critical, 90-day coordinated disclosure window. Recognition section honest about pre-1.0 status (no paid bounty yet, public credit + Discord role + comp credits on Pro/Agency tier for critical findings). NO source code changes (all repo metadata + cleanup). Backend redeploy not required. EXPECTED IMPACT: GitHub repo file list goes from 36 root entries (with auth-*.txt cookie jar files visible to anyone browsing) to a clean ~28 entries. Vercel deploys page goes from ~50% red-X to 100% green once Vercel project itself is disconnected (one-click in Vercel dashboard now that the underlying code trigger is gone). Security researchers landing on /SECURITY.md see a thoughtful policy that addresses SF's actual attack surfaces (LLM-key handling, MCP boundary, agent runtime) rather than generic 'we accept reports' boilerplate.",
3
+ "version": "1.45.0",
4
+ "description": "Open-source GoHighLevel alternative for agencies. 146+ MCP tools that let Claude Code spin up white-labeled client workspaces CRM, booking, intake forms, landing pages, AI chatbot, and pre-wired agent archetypes (speed-to-lead, missed-call-text-back, review-requester) in minutes. AGPL-3.0.",
39
5
  "license": "AGPL-3.0-or-later",
40
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.34.3: LICENSE SWITCH MIT → AGPL-3.0 + REPO CRUFT CLEANUP. Per first-principles analysis (Postiz/Mattermost/Plausible playbook): AGPL protects against closed-source clones while keeping community contributions flowing freely. The hosted Agency tier becomes the de facto commercial license. WHAT SHIPS: (1) LICENSE swapped from MIT to canonical AGPL-3.0 text (fetched from choosealicense GitHub mirror, YAML front matter stripped, 662 lines of canonical license text). (2) README badge changed License: MIT → License: AGPL v3 (teal hex preserved). README footer rewritten to acknowledge the dual-license model and link Postiz/Mattermost/Plausible as inspiration. README self-host section updated to flag the AGPL share-modifications requirement and point at LICENSING.md. (3) NEW LICENSING.md: TL;DR table mapping use cases to license, plain-English explanation of what AGPL means in practice, when commercial license is needed (embedding, white-label SaaS without share-modifications, enterprise legal compliance, due diligence), two paths for commercial use (Path A hosted Agency tier $99/mo for the simple cases, Path B custom commercial license via hello@seldonframe.com for embedding/deep modifications), why we chose AGPL (3 reasons: protects open community, makes commercial value capturable, signals seriousness), what's covered by which license (platform/MCP/docs/skills/blocks/eval all AGPL-3.0; logos and brand assets all-rights-reserved), existing-contributors note about pre-v1.34.3 MIT contributions being treated as relicensed for forward compatibility. (4) CONTRIBUTING.md license section updated to reflect AGPL move with corporate-policy-on-AGPL escape hatch (email hello@seldonframe.com for contributor agreements). (5) REPO CRUFT DELETED: auth-check-redo.txt (Netscape HTTP Cookie File from a curl session, contained stale __Secure-authjs auth tokens — security concern even though expired), DEMO_GIF.md (placeholder runbook for capturing a demo GIF that's now irrelevant since the actual demo lives at seldonframe.com/demo), DEPLOY_DEMO.md (stale deployment guide for a demo subdomain that was never set up; the actual deployment is app.seldonframe.com). (6) QUICKSTART.md REFRESHED to match new README direction: two paths (Hosted recommended / Self-host), updated env-var list to include ENCRYPTION_KEY (required for BYOK encryption), updated monorepo layout, points at /docs/your-business/upgrade-ui and CONTRIBUTING.md for next steps. (7) CUSTOMIZATION.md REFRESHED to be a brief pointer: stops trying to teach 'how to add Stripe/SendGrid/Twilio manually' (those are now MCP tools); instead provides a customization-layers table mapping each need to the right path (skill pack, block, MCP tool, motion preset, DESIGN.md, fork) with cross-links to /docs/your-business/upgrade-ui and CONTRIBUTING.md. (8) MCP package.json: added license field 'AGPL-3.0-or-later' for npm metadata. NO source code changes. NO migrations, NO env vars. Backend redeploy not technically required (license + docs only); MCP package version bumped for SemVer hygiene + so the new license shows on npmjs.com. EXPECTED IMPACT: GitHub visitors landing on seldonframe/seldonframe see the AGPL-3.0 badge (signaling 'this is a real open-source business, not marketing-loss-leader'), can read LICENSING.md to understand the dual-license model, see a clean repo without curl-cookie-jar files or stale demo runbooks. Vercel cleanup follow-up flagged in commit body for the user to handle in Vercel dashboard (can't be done via vercel.json since it's project-level config).",
41
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.34.2: README + CONTRIBUTING CORRECTIONS + AMBITIOUS ROADMAP. User feedback on v1.34.1: (a) README Path A and Path C are both hosted on SF infra (Vercel + Neon) — they're the same backend with different chrome (Claude Code vs web dashboard); (b) booking is SOURCE OF TRUTH, not Google-Calendar-synced — customers book on SF, appointments land in CRM AND sync OUT to Google Calendar; (c) roadmap should be higher-level and contributor-inspiring rather than checking off operational milestones. WHAT SHIPS: (1) README QUICK START RESTRUCTURE. Was three paths (A: Claude Code, B: self-host, C: hosted dashboard). Now two paths: 'Hosted (recommended for most operators)' which covers BOTH Claude Code and dashboard signup as alternative chromes hitting the same Vercel + Neon backend; and 'Self-host' which is the fork-and-run path. Removes the false trichotomy and matches the actual product architecture. (2) BOOKING DESCRIPTION CORRECTED. Was 'Cal-style scheduling with Google Calendar sync' (implies SF is downstream). Now 'source-of-truth scheduling. Customers book on your branded SF page; the appointment lands in your CRM AND syncs out to your Google Calendar in real time. SF is the authority — Google Calendar is a downstream view.' Properly frames SF as the authority. (3) ROADMAP REWRITE — bigger swings, contributor-inspiring. Replaced 5 operational milestones with 9 items grouped 'Soon' (voice + SMS transports, self-improving agents that propose their own skill-pack updates from telemetry, renderer-level motion preset gating), 'Mid-term' (multi-agent orchestration where agents hire other agents — your booking agent calls a fraud-detection agent for high-value bookings, skill-pack marketplace with revenue share for community contributors, vertical templates marketplace for industries beyond the current 6), and 'Long-term — the agent era' (federated agent network with agent-to-agent commerce across SF workspaces, long-running operator agents that run your business for a week and report back, agent fleet operations for agencies running 100+ workspaces from one console). Each item explains the bet so contributors can see what they're stepping into. (4) CONTRIBUTING.md FULL REWRITE (66 → 230 lines). Opens with architecture orientation (the same 6-layer ASCII diagram from README) and the two principles every PR should respect (don't add intelligence to the harness, don't add capability to the skill). Six concrete contribution recipes: Recipe 1 'Add a skill pack' (markdown only, 30 LOC), Recipe 2 'Add an MCP tool' (~30 + 80 LOC), Recipe 3 'Add a block component', Recipe 4 'Tune motion primitives', Recipe 5 'Add a vertical template' (Soul + 8 eval scenarios), Recipe 6 'Improve the eval gate'. Each recipe shows file paths, expected LOC, and what reviewers check. 'What we look for in PRs' explicit lists: Loved / Sent back / Reverted. Notes the active license question (MIT today, considering AGPL-3.0 like Postiz) and explains the dual-license model. NO source code changes — pure repo metadata + docs. NO migrations, NO env vars. Backend redeploy not technically required (only README/CONTRIBUTING change), but bumping MCP package for SemVer hygiene. EXPECTED IMPACT: GitHub visitors see (1) accurate quick-start for SF's actual hosted-backend architecture, (2) correct framing of SF as the authority over scheduling, (3) a roadmap that signals 'this is the future and you can help build it' instead of 'feature checklist'. Contributors landing on CONTRIBUTING.md get architecture orientation + 6 concrete recipes with file paths, LOC estimates, and reviewer expectations — matching the rigor of Twenty's contribute docs.",
42
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.34.1: GITHUB README REWRITE + UI-CUSTOMIZATION DOCS PAGE. Inspired by Postiz (35K stars, $17K MRR), Twenty (45.6K stars), and Motion (the rebranded animation library) — first-principles reflection on what 10x's an open-source GitHub README. Five gaps in the v1.34.0 README: (1) no visual proof — said 'screenshots coming soon'; (2) no architecture diagram — left the thin-harness/fat-skill bet implicit; (3) no code examples beyond install — technical readers leave without seeing the developer experience; (4) the Karpathy bet wasn't articulated — the actual differentiation was buried in 'composable primitives'; (5) no contributor onramp — 'PRs welcome' is dead text. v1.34.1 closes all five. WHAT SHIPS: (1) FULL README REWRITE at packages/crm root. New headline: 'The open-source Business OS you build by typing.' Hero block: a Claude-Code-styled code-fence showing the actual 4-tool-call build sequence (build_landing_page → build_booking_page → build_intake_form → build_website_chatbot → ✓ Live at acme-hvac.app.seldonframe.com). NEW 'The bet: thin harness, fat skill' section explicitly articulating the architectural choice — platform is dumb (140+ MCP tools + block manifest + durable workflows + eval runtime, none of it tries to be smart), behavioral decisions live in markdown skill-packs, antifragile to LLM improvements. NEW ASCII-art architecture diagram showing the 6 layers (Operator → IDE-resident agent → MCP server / thin harness → skill-pack registry / fat skill → Runtime / Next.js+Postgres+Vercel Workflows+motion+MIT → Operator-owned providers). Three install paths (Claude Code MCP, self-host, hosted) with concrete commands. NEW 'What's wired up by default' section listing 10 included surfaces. NEW 'Examples — typical operator prompts' showing 5 real Claude Code prompts and the resulting MCP tool calls. NEW 'What's interesting to contribute to' table mapping 6 codebase areas (MCP tool registry, skill packs, eval gate, block library, motion primitives, workflows) to file paths + 'what's interesting' descriptions — this is the contributor onramp Twenty/Postiz both have. Tech stack section listing every framework concretely. Three-install-path comparison table (cost, workspaces, custom domain, white-label, BYOK, source code, updates, support). NEW 'Bring your own design tools' section listing the four paths (Claude Design, DESIGN.md, v0/Lovable/Cursor, direct fork). Updated roadmap (v1.34.x renderer gating, v1.4 voice+SMS, v1.5 custom block builder, v1.6 skill-pack marketplace, v1.7 agency mode self-serve). NEW 'Why open source' section with three reasons (customer wins, architecture wins, community wins) + acknowledgments to Twenty / Postiz / Cal.com. Open sponsorship slot. (2) NEW DOCS PAGE at packages/crm/src/app/docs/your-business/upgrade-ui/page.tsx. Power-user guide for UI customization. Sections: 'Start with the smart defaults' (frames the balanced preset as the finish line for 80% of operators), 'The four levers in order of effort' (motion preset → DESIGN.md → Claude Design handoff → fork), 'The thin-harness, fat-skill principle' (explains WHY the four levers compose, not just HOW), 'What's deferred and why' (renderer gating, Counter on stat blocks, per-block overrides). Each lever section includes the actual Claude Code prompt that triggers it + the expected response. Wired into the docs sidebar under 'Your business' so it's discoverable from /docs.",
43
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.34.0 (combined with v1.33.2 section-motion wiring): SECTION-LEVEL MOTION PRIMITIVES + apply_motion_preset MCP TOOL + GRACEFUL DISCOVERY SURFACES. Closes the 'how do users discover the UI-upgrade path' question with a graceful-disclosure-with-smart-defaults pipeline. v1.33.2 — wired motion primitives directly into the most-used user-facing block sections (benefits, features, pricing, process, cta) so every workspace inherits richer polish automatically: <Stagger> on benefit/feature/pricing/process grids reveals their children sequentially as the section scrolls into view (childDelay 0.04-0.12s tuned per section); <HoverLift as='span' lift={4}> wraps the primary CTA Link so the call-to-action gets a tactile, brand-tinted glow on hover. Total: 5 section files modified, ~5-10 lines each — minimal surface change, max visual impact. v1.34.0 — added motionPreset field to OrgTheme types ('minimal' | 'subtle' | 'balanced' | 'editorial', default 'balanced'). Each preset has an operator-friendly description used by the apply_motion_preset response. NEW MCP TOOL apply_motion_preset({preset, workspace_id}) routes to NEW POST /api/v1/theme/motion-preset which validates the preset, spread-merges into existing OrgTheme, persists. Today the renderer applies the 'balanced' set's primitives unconditionally; preset gating in renderers ships in v1.34.x. The preset's primary value today is storing OPERATOR INTENT — Claude Code reads it via get_workspace_state and uses it as a hint when generating new content (e.g. don't add Counter to a workspace that picked 'minimal'). DISCOVERY SURFACES (the antifragile graceful-disclosure pipeline). finalize_workspace response now includes a structured next_steps_available array: [apply_motion_preset, apply_design_md, import_claude_design_handoff, update_landing_content/configure_booking/customize_intake_form] with per-tool 'when' triggers + example calls. The human-facing summary string also adds an 'Optional upgrades (when you're ready)' section listing the same 4 paths. FIRST_CALL_BANNER updated to mention all three customization tools (apply_motion_preset, apply_design_md, import_claude_design_handoff) with explicit guidance: 'All three tools are OPTIONAL — surface them only when the operator's vibe asks for it (\"make it feel premium\", \"I have a brand kit\", \"I just exported from Claude Design\"). Never push these on a user who just wants a working site.' Pipeline philosophy: SMART DEFAULTS at workspace creation (every site ships polished automatically — RevealOnScroll on sections, Stagger on grids, HoverLift on CTAs), LEVERS exist for those who explore (apply_motion_preset for intensity tuning, apply_design_md/import_claude_design_handoff for full design-system replacement), CLAUDE CODE ROUTES INTELLIGENTLY based on conversation vibe (never blocks, always offers). Two new MCP tools (apply_motion_preset + the existing apply_design_md/import_claude_design_handoff from v1.33.1), one new CRM route (/theme/motion-preset), one type addition (MotionPreset enum + OrgTheme.motionPreset?), no migrations, no env vars. Backend redeploy required.",
44
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.33.1: BRING-YOUR-OWN-DESIGN-SYSTEM MCP TOOLS — apply_design_md + import_claude_design_handoff. Closes the workflow gap where third-party AI design tools (Anthropic Claude Design, Google Labs design.md, v0, Lovable, etc.) produced output that operators had to manually translate into SF tool calls. Now Claude Code can pipe their output directly into a workspace via two new MCP tools. WHAT SHIPS: (1) NEW MCP TOOL apply_design_md. Accepts the FULL CONTENT of a DESIGN.md file (the Google Labs format: YAML front matter for tokens + Markdown for rationale) as a string. The MCP-client process reads the file in the operator's environment (e.g. fs.readFile in Claude Code) and passes the content to the server, which parses YAML front matter, extracts tokens, maps tokens.colors.primary / tokens.colors.accent / tokens.mode / tokens.typography.body to OrgTheme fields, and applies. Tokens that don't map 1:1 (spacing scales, custom shadows, component-specific tokens) come back in `unmapped` so Claude Code can decide whether to surface them via update_landing_page (as CSS custom properties on individual pages) or just inform the operator. Server caps at 256KB. Token-reference resolution supported (e.g. {colors.primary.500} → resolves through the YAML tree). Handler routes to NEW POST /api/v1/theme/apply-design-md. (2) NEW MCP TOOL import_claude_design_handoff. Accepts a Claude Design handoff bundle as a JSON object — the artifact Claude Design produces when a design is ready for code (HTML or React components + design tokens + asset URLs). Defensive bundle schema (Anthropic hasn't published a formal spec yet): { meta?: { project_name?, target? }, tokens?: { colors?, typography?, mode? }, components: [{ name, surface?, react_source? OR html_source?, props_schema?, deps? }], assets? }. Server applies any embedded tokens to OrgTheme via the same path as apply_design_md, validates each component (name presence, surface enum, source size, props_schema shape) and returns a structured manifest with truncated source previews + per-component 'next_step' instructions for wiring them via update_landing_page or add_custom_block. CRITICAL — does NOT auto-execute generated React on live customer pages. Components route through human/eval review (the same gate that protects published agents). Customer-facing surfaces still run through the eval gate before publish; Claude Design output isn't trusted to bypass that. Limits: 1MB total bundle, 64KB per component source, 40 components per import. Handler routes to NEW POST /api/v1/handoff/import. (3) NEW CRM ROUTES at packages/crm/src/app/api/v1/theme/apply-design-md/route.ts and packages/crm/src/app/api/v1/handoff/import/route.ts. Both use the existing v1-identity auth, demo-readonly + assertWritable guards, log-event observability, and OrgTheme spread-merge pattern. yaml dependency (already installed at ^2.8.4) used for parsing. Architectural pattern: thin parse + map + apply on the server, fat skill (when to apply, which surfaces to wire, how to compose with other tools) in Claude Code via the SF MCP. As frontier models improve at parsing third-party design output formats, every operator's customization workflow gets richer without us updating the CRM. NO migrations, NO new env vars. Backend redeploy required. EXPECTED IMPACT: an operator who designed a page in Claude Design or has a DESIGN.md file from their brand team can now apply it to their SF workspace in one Claude Code prompt: 'apply my DESIGN.md' or 'import this Claude Design handoff'. Closes the loop on SF as the open, MCP-native, design-tool-agnostic Business OS — operators bring whatever AI design tool they prefer, output flows through Claude Code into SF without manual translation. Per the Becker frameworks insight: the value isn't the design tool, it's the wired-up infrastructure that accepts whatever the operator already designed.",
45
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.33.0: USER-PUBLISHED PAGES INHERIT MOTION POLISH AUTOMATICALLY (CRM-side; MCP package version bumped for SemVer hygiene). v1.32.1 shipped 8 motion primitives (RevealOnScroll / Stagger / HoverLift / Counter / TextReveal / Marquee / MagneticButton / Parallax) but they were standalone — operators had to wire them in manually (or via Claude Code) for any visual benefit. v1.33.0 wires the most impactful primitive — RevealOnScroll — into the user-facing page renderer so every published landing page inherits scroll-triggered reveals automatically. WHAT SHIPS: (1) page-renderer.tsx now wraps every section below the fold in <RevealOnScroll distance=20 duration=0.55 margin=-100px>. The first section (assumed hero / above-the-fold) renders directly with no animation — preserves LCP / immediate paint. Subsequent sections fade + slide up 20px as they enter the viewport, with -100px margin so the animation kicks slightly before the section is fully visible (feels lively, not pop-in). (2) PageRenderer stays a React Server Component. RevealOnScroll has 'use client'; only the thin wrapper hydrates. The block content rendered inside (manifest.render(...)) stays server-rendered. SEO / LCP / streaming all unaffected. Wrapper rendered with className='block w-full' so it doesn't disrupt the section's layout. (3) Per the antifragile motion philosophy: this is the dumbest, safest primitive applied universally. The 'skill' of when to compose richer motion (TextReveal on hero headlines, MagneticButton on CTAs, Stagger on grid blocks, Counter on stat blocks) lives in Claude Code via the SF MCP — applied per-page based on the operator's intent. As frontier models improve at picking and composing primitives, every user's pages get richer without us shipping new code. NO new dependencies (motion already installed), NO migrations, NO new env vars, NO MCP tool changes. Backend redeploy required. EXPECTED IMPACT: every operator with a published landing page wakes up tomorrow with a tangibly more polished site — sections fade up as visitors scroll, instead of just being there from the start. The Linear-class 'this site feels expensive' signal applied universally, with zero operator action required. Future ships can wire the other primitives (Stagger on grid blocks, Counter on stats) into specific block types in the manifest, and add `apply_motion_preset` MCP tools so Claude Code can elevate any page on demand ('make my hero more impactful' → composes 4-5 primitives).",
46
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.32.1: MARKETING — REALISTIC DASHBOARD HERO + COPY FIXES + MOTION PRIMITIVES LIBRARY. (1) HERO RIGHT-PANE redesign. The v1.32.0 right pane showed a stylized landing-page mockup that read as 'designer illustration' rather than 'real product.' v1.32.1 replaces it with a credible admin dashboard view: browser chrome (URL app.seldonframe.com/dashboard), 34/66 split with left sidebar (workspace tile 'Acme HVAC' + nav items) and right main content (Pipeline kanban with 4 columns: New Lead / Quoted / Scheduled / Won). Sidebar nav items light up with green glowing dots sequentially as the build tools fire on the left pane (Pages at 1.5s, Bookings at 2.5s, Intake Forms at 3.5s, Agents at 4.5s — matching the tool-call timing). Kanban cards appear in their columns at staggered times: 'AC repair · 5012 N 32nd St · $340' lands in Quoted at 2.6s, 'Furnace tune-up · Glendale · $120' lands in Quoted at 3.6s, 'AC install · May 10 · 2pm · $4,800' lands in Scheduled at 4.6s. Bottom-right shows live agent indicator 'Acme HVAC Bot · v1 · live · 200 ok'. Helper components added: NavIcon (6 inline SVG icons), NavGroup (animated label + children), NavItem (with optional lightUpAt prop for the green-dot reveal), KanbanColumn (label + count + cards with appear-at delays). (2) COPY FIXES. Case study heading 'What 12 minutes of Claude Code looks like' → 'What 5 minutes of Claude Code looks like'. Body 'on camera in 12 minutes' → 'on camera in 5 minutes'. Stat tile '12 min build to live' → '5 min build to live'. Aligned with the hero's 'under 5 minutes' claim. Infrastructure section link '→ Browse 25+ MCP servers for SMB operators' → '→ 140+ MCP tools your agent can call' (now points to /docs instead of /docs/mcp-servers). Bigger number, more accurate framing — these are SF's own MCP tools, not third-party servers. (3) NEW MOTION PRIMITIVES LIBRARY at packages/crm/src/components/motion/. Antifragile design: thin wrappers around motion/react with sensible defaults; Claude Code composes them into rich page experiences via natural-language prompts. Eight primitives shipped: <RevealOnScroll> (fade + slide up on scroll-into-view, configurable distance/duration/delay/once/margin/as), <Stagger> (children reveal one-by-one with childDelay between), <HoverLift> (hover-lift + accent glow on cards, configurable lift + glowColor), <Counter> (animate from 0 → value when scrolled into view, with prefix/suffix/separator/decimals options), <TextReveal> (split text by word, reveal word-by-word with wordDelay), <Marquee> (infinite horizontal scroll for logo bars, configurable speed/direction/pauseOnHover/gap), <MagneticButton> (button subtly attracted to cursor with spring physics + configurable strength), <Parallax> (scroll-linked Y translate, configurable speed). PRESETS object exports reusable transition configs (reveal, pop, fade) for callers rolling their own motion.div. EASE_OUT_EXPO constant exported for shared easing curve. Each primitive accepts theme-agnostic className overrides; defaults match the SF brand (teal accent, restrained motion durations, conservative distances). Operators don't tune these — Claude Code does, picking and composing primitives based on the user's intent. As frontier models improve at this composition, every SF user's pages get richer without us shipping new animation code. Index file at components/motion/index.ts re-exports primitives + types. NO migrations, NO new env vars, NO MCP tool changes. Backend redeploy required. EXPECTED IMPACT: visitors see a credible admin CRM (kanban + sidebar) in the hero — 'this is real product' rather than designer mockup. Numbers across the page are consistent (5 min, 140+). Motion primitives are available from any user-facing block; future ships can wire them into landing-section-renderer.tsx as defaults so every user's published page automatically inherits the polish without explicit operator action.",
47
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.32.0: MARKETING — HERO + CTA + COPY OVERHAUL (Becker frameworks + Hormozi value equation + 12yo language). User feedback: 'eval gate IS NOT what clients want.... they want a fully personalized and already wired ai-native business OS created in less than 5 minutes using natural language in claude code.... fully customizable to their specific edge cases. dumb down language to 12 years old, cut out any fluff text, answer the $100M offer value equation. CTA should be install MCP — only working path.' Inspired by Alex Becker's video on the future of SaaS — frameworks (CRM + landing page + calendar + intake) wired together with same branding, customizable per business, no Zapier breaking. v1.32.0 rewrites the marketing site around this. WHAT SHIPS: (1) HERO REWRITE. Replaced AgentEvalCard (which showed eval gate flipping green) with BuildAndShowCard — a two-pane animated mock that's the launch story's actual money shot. LEFT pane (40%): Claude Code terminal showing user prompt 'Build a website for Acme HVAC. Phoenix, AZ. AC repair and install. Phone (602) 555-0188.' followed by 4 sequential MCP tool calls (build_landing_page / build_booking_page / build_intake_form / build_website_chatbot) firing every second with green '200 ok' status pills, ending with '✓ Live at acme-hvac.app.seldonframe.com'. RIGHT pane (60%): a mini-browser preview showing the landing page being built in sync — empty skeleton with spinner first, then navbar with 'Acme HVAC' wordmark, then hero section ('AC repair, fast.'), then booking calendar (14-day grid with one date highlighted teal), then intake form (Name / Phone / Submit), and finally a chatbot bubble that pops in bottom-right. Total animation ~6 seconds, plays once on load. Hits Hormozi's value equation: Dream Outcome (complete wired Business OS visualized), Likelihood (shown working in <7s), Time (animation IS the proof of '<5 min'), Effort (one prompt builds everything). (2) HERO COPY rewrite. Headline went from 'Build a complete AI-native Business OS with natural language' to 'Your website, CRM, calendar, and AI chatbot. Built in under 5 minutes by typing what you want.' — concrete (lists what you get), specific time, plain action verb. Subhead from 'CRM, website, AI agents, and automations — all in one workspace, built and updated through natural language with Claude Code. Eval-gated before going live.' to 'One sentence in Claude Code. SeldonFrame builds your landing page, booking calendar, intake forms, and CRM — all linked, same brand. Change anything by saying so. No Zapier. No code. No duct tape.' — Becker-language ('linked, same brand', 'no Zapier'), 12yo reading level. (3) CTA OVERHAUL. Primary CTA across hero, nav, FinalCTA changed from 'Start for free → /signup' to 'Install the MCP → #install' (anchors to HowItWorks section). Below hero CTAs: NEW <InstallCommandPill> showing the actual command 'claude mcp add seldonframe -- npx -y @seldonframe/mcp' with one-click copy button (clipboard icon → green checkmark + 'copied' on success, resets after 1.8s). Secondary hero CTA changed from 'Watch the demo' to 'See a live build → /demo'. (4) HOWITWORKS rewrite + #install anchor. Three steps reframed for Becker-frameworks language: Step 1 'Install in Claude Code' with the actual install command. Step 2 'Tell it about your business' with the example prompt. Step 3 'Get your wired-up business' with the live URL. Section heading changed from 'How it works · Three steps. About a minute. A complete Business OS, live in production.' to 'Three steps. Under five minutes. · No drag-and-drop. No setup wizards. No \"let's hop on a call.\" Just type, and it builds.' Section gets id='install' so hero CTA scrolls smoothly here. (5) FEATURESTORIES rewrite. Three new stories: Story 1 'Type, don't click — One prompt. Five tools built.' (uses ClaudeCodeMockVisual). Story 2 'Wired. No Zapier — Your tools share one brain.' (uses NEW <WiredFrameworkVisual>: hub-and-spokes diagram with SF logo at center, 4 framework tiles Website/Calendar/Forms/CRM in corners connected by faint teal dashed lines, bottom caption 'Same database · Same brand · Same admin'). Story 3 'Edge cases? Say so — Change anything by typing the change.' (uses NEW <EditAnythingVisual>: Claude Code mock showing a follow-up prompt 'Raise the AC repair price from $89 to $99. Add Glendale to service areas.' followed by 3 tool calls update_agent_pricing / update_landing_page / run_agent_evals all green, ending with '✓ Updated · v3 → v4 · live'). (6) FINALCTA rewrite. Headline 'Start building in five minutes' → 'Stop stitching. Start typing.' Subhead replaced. CTAs aligned with new install-MCP message. (7) DEAD CODE REMOVAL. Deleted unused RuntimeRegenVisual + EmbedBubbleVisual components (used in v1.31.1 but superseded by WiredFrameworkVisual + EditAnythingVisual + the BuildAndShowCard's chatbot bubble). NO new dependencies, NO migrations, NO new env vars, NO MCP tool changes. Backend redeploy required. EXPECTED IMPACT: a launch-video viewer landing on seldonframe.com sees the actual product getting built in real-time within 7 seconds — not abstract claims, not 'eval gate' jargon they don't care about. Headlines and copy speak at 12yo reading level. CTA tells them exactly what to do (install the MCP) and shows the command. Becker's framework insight is the spine: SF gives you frameworks, wired together, with one brand, fully customizable by saying so. Hormozi's value equation hit across hero, HowItWorks, FeatureStories, FinalCTA: high dream outcome, high likelihood, low time, low effort.",
48
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.31.3: MARKETING — CASE STUDY + REPLACES-COMPARISON + ROUTE COLLISION HOTFIX (CRM-side; MCP package version bumped for SemVer hygiene). v1.31.2 build failed on Vercel: my new (marketing)/privacy and (marketing)/terms pages collided with pre-existing app/privacy and app/terms pages I missed during the earlier audit. Production stayed at v1.31.1 (cd120ee). v1.31.3 ships the hotfix + the planned case-study work in a single commit. WHAT SHIPS: (1) HOTFIX — deleted (marketing)/privacy/page.tsx and (marketing)/terms/page.tsx. The pre-existing app/privacy/page.tsx and app/terms/page.tsx already had substantial content (10-section privacy policy + 10-section TOS, last updated April 2026, dashboard-themed) that's better than my minimum-viable replacements. The footer links never 404'd; my audit was wrong. /blog index + /blog/why-mcp from v1.31.2 stay in place since they're net-new with no sibling collisions. (2) NEW <CaseStudy> component, replacing the centered CTA-card <SeeItBuilt>. Two-column layout: left side has 'Worked example' pill, h2 'What 12 minutes of Claude Code looks like', 2 paragraphs framing Desert Cool HVAC honestly as a worked-example fixture (not a fabricated customer) — narrative covers Phoenix, AZ context, the 5 tools they came from (Salesforce/Cal.com/Mailchimp/Intercom/Webflow), and what got built (landing page, booking, intake, CRM with HVAC fields, published chatbot). CTA 'Watch the walkthrough →' → /demo. Right side: 4-stat grid (12 min build-to-live, 5→1 tools replaced, 8/8 evals passed, 0 lines hand-edited) with motion-staggered scale-in. (3) NEW <Replaces> component. Visual story for the consolidation pitch: 5 generic tool tiles (CRM / Scheduler / Email tool / Chatbot / Site builder) with diagonal strikethrough overlays + dimmed colors, a teal arrow, then the SF logo+wordmark in a glowing card. Heading 'Replaces the stack you've been duct-taping together', subhead 'Five tools, one workspace. Same database. Same brand. Same admin. And the AI agents come with it.' Tool tiles use category labels (not competitor brand names) to avoid trademark risks; the visual shape carries the message. (4) PAGE COMPOSITION updated: <SeeItBuilt> removed, <CaseStudy> + <Replaces> inserted between <HowItWorks> and <Pricing>. New flow: Nav → Hero → FeatureStories → HowItWorks → CaseStudy → Replaces → Pricing → Infrastructure → FinalCTA → Footer. NO new dependencies, NO migrations, NO new env vars, NO MCP tool changes. Backend redeploy required. EXPECTED IMPACT: visitors who scroll past the hero and feature stories now hit a credible end-to-end story (12 minutes, 5→1, here's what got built) and a visual gut-punch for the consolidation pitch (5 dimmed tool tiles → SF wordmark). Frames the Desert Cool HVAC fixture honestly as a worked example so credibility holds. The build also unsticks production from v1.31.1 — Vercel will now successfully deploy v1.31.0 hero + v1.31.1 feature stories + v1.31.2 blog index + v1.31.3 case study + replaces all together.",
49
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.31.2: MARKETING SITE — KILL 404s + FIRST REAL BLOG POST (CRM-side; MCP package version bumped for SemVer hygiene). The marketing footer linked /privacy, /terms, /blog — but /privacy and /terms 404'd entirely, and /blog was a 'Coming soon' stub. Three credibility leaks for launch-week visitors. v1.31.2 fills all three. WHAT SHIPS: (1) NEW (marketing)/privacy/page.tsx. Plain-English privacy notice — what we collect (account info, workspace data, anonymous usage telemetry), what we don't (LLM keys readable in plaintext, payment data, fingerprints), how we use it (operate the product, no model training, no resale, no aggregation), where it lives (Postgres on Neon, Vercel hosting), sub-processors list, your-rights section (export anytime, delete account, contact privacy@), changes notice, contact. Marked as minimum-viable for launch — flagged in source-comment to be replaced with a lawyer-reviewed full policy before scaling beyond launch-week traffic. (2) NEW (marketing)/terms/page.tsx. Plain-English TOS — account responsibility, allowed uses (legitimate business ops, for-profit + non-profit), disallowed uses (spam, illegal content, deceptive AI agents, reverse-engineering, reselling without Agency tier), data ownership ('Workspace data is yours'), open-source MIT license note, billing flow (failed payment retries), service level (99.9% target on Pro+; SLA only on Agency), liability cap (12 months of fees), changes notice. Same minimum-viable framing. (3) REWROTE (marketing)/blog/page.tsx. Was a 50-line 'Coming soon — follow @seldonframe' stub. Now a real index with 3 posts: 'Why we built SeldonFrame on MCP' (live, links to /blog/why-mcp), 'How the eval gate works' (coming soon marker), 'BYOK is not a feature, it's the deal' (coming soon marker). Each post card shows date + author + lede; live posts have a 'Read post →' arrow that animates on hover; 'coming soon' posts have a uppercase 'Coming soon' chip and reduced opacity but stay in the visual rhythm. Bottom CTA points to X + Discord for new-post notifications. (4) NEW (marketing)/blog/why-mcp/page.tsx. First real launch-week blog post (~700 words). Sections: 'The default architecture is wrong' (chatbot-wrapper playbook critique), 'What changed: agents have IDEs' (Claude Code / Cursor / etc. + MCP), 'The bet: expose everything as tools' (140+ MCP tools mapping to dashboard surfaces), 'Why this works' (3 reasons: agent faster than click-through, agent improves automatically with model upgrades, dashboard stays simple), 'What we gave up' (marketing-page conversion friction), 'What's next' (preview of upcoming posts on eval gate / BYOK / durable workflows). Footer with 'All posts' back-link + GitHub source link. Authentic voice — no marketing fluff, real architecture argument. (5) NEW .marketing-prose CSS class in globals.css. Mirror of .docs-prose but with hardcoded marketing palette (#fafafa foreground, #a1a1aa muted, #1FAE85 accent, white/5 borders) so the prose renders correctly on the always-dark marketing pages regardless of root theme setting. NO new dependencies, NO migrations, NO new env vars, NO MCP tool changes. Backend redeploy required. EXPECTED IMPACT: launch-week visitors clicking the footer's Privacy / Terms / Blog links land on real pages — no 404s. The first real blog post (Why MCP) is a credible architecture argument that signals 'this team thinks about the work' to the developer audience, who are the people most likely to share the launch with SMB operator friends. Minimum-viable on legal pages keeps options open without blocking launch on lawyer review.",
50
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.31.1: MARKETING SITE — FEATURE STORIES (\"show, don't tell\") (CRM-side; MCP package version bumped for SemVer hygiene). The marketing site's middle was a flat 6-up Features grid (Per-client branding / Agents with memory / Closed-loop attribution / Natural language scaffolding / Approval gates / Cost visibility) that read like a feature list, not a launch site. Above it, a 'Who is SeldonFrame for?' Personas section with 2 emoji-icon cards (🛠️ Build for yourself, 🏢 Build for your clients) felt like a Notion template. Both sections used the same vertical rhythm as everything else (centered h2 + grid of cards), making the page visually monotonous. v1.31.1 replaces both with three story-driven feature blocks. WHAT SHIPS: (1) NEW <FeatureStories> component in (public)/landing-client.tsx. Three feature stories in alternating image-left/image-right layout, each with a small CSS+SVG product mockup beside body copy. Section heading 'What makes SeldonFrame different' + supporting subhead. (2) STORY 1 — Build with Claude Code (image-right). Pill 'Build with Claude Code'. Heading 'Describe what you want. Watch it ship.' Body emphasizing the 140+ MCP tools and the launch-flow continuity ('the launch story isn't a demo, it's the actual flow'). Visual: Claude Code-styled terminal showing user prompt → 4 sequential MCP tool calls (get_workspace_state / build_website_chatbot / run_agent_evals / publish_agent) each with green status pills → 'Acme Dental Chatbot is live' confirmation. CTA → /docs/getting-started/connect-claude-code. (3) STORY 2 — Eval-gated, regen on fail (image-left). Pill 'Eval-gated, regen on fail'. Heading 'Agents you can actually trust in production.' Body explains the 8-scenario suite + the runtime regeneration on critical-fail (the v1.28.6 architecture). Visual: 'agent runtime · live' card showing an amber 'critical-fail validator' warning (no_state_change_hallucination), a 'regenerate with correction prompt' arrow, and a green 'regenerated · clean' result with the corrected agent response. CTA → /docs/agents/eval-gate. (4) STORY 3 — Embed anywhere (image-right). Pill 'Embed anywhere'. Heading 'One script tag. Brand-themed. Mobile-friendly.' Body covers the embed snippet, brand-themed styling, SF-hosted pages getting wired automatically, and conversation logging back to CRM. Visual: aspect-4/3 fake-website mockup with skeleton content rectangles, a chatbot bubble in the bottom-right (gradient teal-to-darker-teal with shadow), and a speech-bubble greeting 'Hi! Looking to book a service today?'. CTA → /docs/agents/embed. (5) <FeatureStory> primitive component handles the alternating layout, the pill, headline, body, CTA, and the visual slot. <ClaudeCodeMockVisual>, <RuntimeRegenVisual>, <EmbedBubbleVisual> are pure CSS+SVG (no images, theme-aware, fast). (6) Removed <Personas> + <Features> components from the page composition. The audience-defining message Personas carried (solopreneur vs agency) implicitly resolves through the feature stories themselves; explicit personas section was redundant and visually weak. Page composition now: Nav → Hero → FeatureStories → HowItWorks → SeeItBuilt → Pricing → Infrastructure → FinalCTA → Footer + DiscordFloat. NO new dependencies, NO migrations, NO new env vars, NO MCP tool changes. Backend redeploy required. EXPECTED IMPACT: the marketing site middle goes from 'list of features in cards' to 'three product stories with motion' — Linear-style. Each story has a clear visual the eye can latch onto and a single CTA into the docs. Page rhythm varies (alternating left/right) instead of repeating centered grids. Visitors who scroll past the hero now see what SF actually looks like operating, not just words about what it does.",
51
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.31.0: MARKETING SITE — HERO REWRITE + PRODUCT VISUAL (CRM-side; MCP package version bumped for SemVer hygiene). The seldonframe.com hero showed a small terminal mockup with a `claude mcp add` install command and a workspace-creation success block — true to the install flow, but it didn't show the launch story's ACTUAL magic moment: the eval gate flipping each scenario green before an agent goes live. Visitors landing on the marketing site never saw a product visual; the whole pitch was text. v1.31.0 fixes that. WHAT SHIPS: (1) NEW AnimatedEvalCard mock in (public)/landing-client.tsx. Pure CSS+SVG (no images, theme-aware, fast). Composition: browser chrome (mac dots + URL `app.seldonframe.com/agents/acme-hvac/evals`); agent header row showing 'Acme HVAC Chatbot' + version 'v3' + animated 'Live' status pill that appears at 2.6s; eval body with 'Eval gate' label + animated `8/8 passed` counter + progress bar that fills from 0% to 100% over 1.6s; 8-scenario grid (Greeting / FAQ accuracy / Booking / Reschedule / Refusal / PII handling / Escalation / Tone consistency) with each scenario's checkmark spring-popping in sequentially over ~1.4s; bottom 'Publish unlocked' pill with the green-checkmark glyph and '≥ 87.5% threshold' caption that appears at 2.8s. Total animation ~3 seconds, paced for first-impression — no loop, plays once on hero load. (2) HERO COMPOSITION REWORK. Heading scaled up (clamp(36px,5.5vw,64px) was 34px,5vw,56px), tracking tightened (-0.04em was -0.035em). Subhead rewritten from abstract 'Composable primitives to create customized business operating systems...' to concrete 'CRM, website, AI agents, and automations — all in one workspace, built and updated through natural language with Claude Code. Eval-gated before going live.' Primary CTA changed from 'Start for $0' (technical) to 'Start for free' (operator-language) and pointed at /signup (visitor → operator funnel) instead of /docs/quickstart (developer flow). Secondary CTA 'Watch the demo' kept. Subtle radial-gradient glow added behind the hero (rgba(31,174,133,0.18) → transparent radial) for Linear-quality depth. Section max-width grew from 1140 to 1180 to accommodate the wider eval card. (3) NAV CTA updated to match: 'Start for $0' → 'Start for free', /docs/quickstart → /signup. (4) FINAL CTA at bottom updated: 'Quick start' → 'Start for free', /docs/quickstart → /signup. Secondary 'Read the docs' kept. NO new dependencies, NO migrations, NO new env vars, NO MCP tool changes. Pure (public)/landing-client.tsx + welcome.js + package.json. Backend redeploy required. EXPECTED IMPACT: launch-video viewers landing on seldonframe.com see a real product visual within 2 seconds — the eval gate filling up green, scenarios checking off, 'Publish unlocked' appearing. The launch story's actual magic moment becomes the hero's centerpiece, not a buried subsection. The hero now matches Linear-quality: bigger heading, confident gradient, animated product UI, operator-language CTAs. Five more ships in v1.31 series will replace the flat 6-up Features grid with story-driven feature blocks (v1.31.1), build out the Desert Cool HVAC case study (v1.31.2), real /blog index + /privacy + /terms (v1.31.3), and final polish (real wordmark SVGs, mobile pass, OG image refresh, Lighthouse audit) (v1.31.4).",
52
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.30.2: /docs CONTENT + LOGO POLISH (CRM-side; MCP package version bumped for SemVer hygiene). v1.30.0 shipped the /docs scaffold (header, sidebar, homepage) but every sidebar link was an anchor placeholder (#chatbot, #workspaces) and the header logo was a literal 'SF' text div. v1.30.2 fills both gaps. WHAT SHIPS: (1) REAL LOGO IN /docs HEADER. Replaced the placeholder 'SF' text in app/docs/docs-header.tsx with the actual SeldonFrame icon SVG (/brand/seldonframe-icon.svg) — same brand-isolated icon the dashboard sidebar uses. Wordmark text now reads 'SeldonFrame Docs' instead of bare 'Docs', creating visual continuity between the dashboard and /docs. (2) 34 REAL ARTICLE PAGES. Each of the 33 sidebar items (8 categories × 4-5 items each) plus the /docs/learn footer link now resolves to a real, navigable page at app/docs/<category>/<slug>/page.tsx — no more #anchor placeholders. Categories: Getting started (4), Your business (4), Customers/CRM (4), AI Agents (5), Pages & website (4), Email & Automation (4), Integrations (5), Billing & plans (3), plus /docs/learn. Sidebar updated to point at /docs/<category>/<slug> routes; docs-sidebar.tsx open-state defaults updated for renamed 'agents' category id (was 'ai-agents'); active-state highlight now uses startsWith so deep routes still highlight their parent category in the sidebar. (3) NEW app/docs/article-shell.tsx. Shared layout component every article wraps in: breadcrumb (Docs › Category), big H1 + lede paragraph, prose body, footer with 'Back to all docs' + 'Edit on GitHub' link. Plus inline helper components: <Step n=1 title=...> (numbered how-to steps), <ComingSoon> (dashed-border 'Coming soon' marker), <Callout variant=info|tip|warn>, <CodeBlock>, <InAppLink href=>. Articles compose from these primitives — every page reads consistently. (4) docs-prose CSS in globals.css. ~30-rule body-style block scoped to .docs-prose: tighter line-height (1.75), restrained heading scale (h2 1.5rem semibold, h3 1.125rem), comfortable line length, theme-aware code/em/blockquote/list styling. Rolled our own instead of @tailwindcss/typography for exact control. (5) ARTICLE CONTENT SHAPE. Linear-style: terse, opinionated, lots of links to in-app surfaces and other docs (so navigation feels alive). Each Getting Started + AI Agents + Pages article goes deep with real content (60-second walkthroughs, code examples, eval-gate explanation). Your business / Customers / Email-Automation / Integrations / Billing articles are concrete but more compact (still real content, not lorem ipsum). The 'Voice + SMS' page intentionally signals 'coming soon' rather than faking unreleased capability. (6) lucide-react icon fix. ArticleShell originally tried to import 'Github' which doesn't exist in the installed lucide-react version; swapped to 'Pencil' for the 'Edit on GitHub' link. Caught at typecheck. NO migrations, NO new env vars, NO MCP tool changes. Backend redeploy required. EXPECTED IMPACT: launch-video viewers landing on /docs see the SF logo (not 'SF' text), can click any sidebar item and land on a real article (not a stuck-in-place anchor scroll), read content with proper typography (not unstyled <h2>/<p>/<ul>), and trust the documentation enough to actually use SF. The sidebar is now genuinely a navigation surface, not a wishlist.",
53
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.30.1: HOTFIX — v1.30.0 build failed on Vercel because the new app/docs/ route collided with an existing app/(public)/docs/ route. Both resolved to /docs. Next.js parallel-routes error halted the build, leaving production stuck on v1.29.1. The old (public)/docs/ was an API reference page (workspaces / secrets / Seldon It / Brain / OpenClaw curl examples) created earlier in the year. The new app/docs/ is the Linear-class user-facing documentation hub. They serve different audiences (developers vs. operators) and overlap in content — but cannot coexist at the same URL. Decision: keep the new Linear-class /docs as the primary surface (operator-facing is the launch priority), delete the old API-reference /docs (content lives on in git history at cb8d3d2 and can be revived as a /docs/api article when docs gets deeper article pages). Also removed the now-orphaned components/docs/api-docs-code-block.tsx + the empty components/docs/ directory. NO behavior change beyond unblocking the build. The new Linear-class /docs route from v1.30.0 ships unchanged. NO migrations, NO new env vars, NO MCP tool changes. @seldonframe/mcp version bumped for SemVer hygiene.",
54
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.30.0: LINEAR-CLASS /docs PAGE (CRM-side; MCP package version bumped for SemVer hygiene). The sidebar's 'Docs' link pointed at /docs but the route didn't exist (404). v1.30.0 builds the entire route from scratch as a standalone documentation surface (outside the dashboard route group), modeled on linear.app/docs structure. WHAT SHIPS: (1) NEW app/docs/layout.tsx — standalone shell, no dashboard chrome. Wider max-w-1080px content area, generous py-16 vertical breathing room, sticky top header + collapsible left sidebar + main column. (2) NEW app/docs/docs-header.tsx — Linear-style top bar: SF logo + 'Docs' label on left; centered search input with ⌘K kbd hint; theme toggle + Sign in + Sign up CTA on right. Sticky, backdrop-blurred. Search is non-functional in v1.30.0 (placeholder for v1.30.1 with content indexing). (3) NEW app/docs/docs-sidebar.tsx — collapsible category nav with 8 sections (Getting started / Your business / Customers (CRM) / AI Agents / Pages & website / Email & Automation / Integrations / Billing & plans), 38 article links total. Each category expands/collapses; current page highlighted. Bottom 'meta' nav: Docs / Developers (→ GitHub) / Learn / Contact support (→ Discord). (4) NEW app/docs/page.tsx — homepage with: HERO (4xl/5xl heading + subtitle), POPULAR GRID (4 large cards: 3-minute demo / Build a chatbot / First workspace / Claude Code MCP), QUICK START STEPPER (4 numbered steps with code blocks: install MCP / build workspace / add chatbot / customize from dashboard), CORE CONCEPTS GRID (6 small cards: Soul / Agents / Templates / Workspaces / Customers / Bookings), 6 DETAILED CATEGORY BLOCKS (each is icon + title + 4 link rows in a unified card grid), LEARN section (video tutorials + customer stories), and a final 'Build your first workspace' CTA card with gradient. CRAFT DETAILS: hover lifts on cards (border-primary/30 + bg-accent/20 + icon color shift); arrow icons translate-x on hover; primary-tinted icon backgrounds throughout; tabular spacing; generous whitespace between sections (space-y-16 on hero/section gaps). All article hrefs are anchor placeholders (#chatbot, #workspaces, etc.) for v1.30.0; individual article pages get authored over time without changing nav structure. Sidebar 'Docs' link now actually works (was 404). Compatible with the existing app shell — uses next-themes for dark/light toggle, lucide-react for icons (already-installed deps); no new packages. NO migrations, NO new env vars, NO MCP tool changes. Backend redeploy required. EXPECTED IMPACT: launch-video viewers landing on /docs see a polished Linear-class documentation surface that signals 'this product is real and well-built.' SF clients curious about features can now actually find them organized cleanly. /docs becomes the surface that transforms 'is this real?' into 'I want this.'",
55
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.29.1: BODY COPY + EMPTY STATES (LINEAR-CLASS CRAFT) — second pass on the v1.29.0 vocabulary work, focused on per-page polish: typography, empty-state heroes, and the dashboard welcome heading the operator sees the moment they log in. WHAT SHIPS: (1) /landing EMPTY STATE HERO. Was: small 'Create your first landing page — Choose a template to get started' card. Now: full hero card with circular icon ('Layout' in primary tint), 'Build your first page' h2, plain-English subtitle ('Pages live on your public website — landing pages, lead forms, booking flows. Start from a template below or build from scratch.'). Template grid below promoted with hover transitions to primary-tinted icon backgrounds — micro-interaction polish. Header subtitle on /landing/page.tsx itself rewritten from 'Build and publish modular pages with integrated [intake form] and booking sections' to 'Your business's public-facing pages — landing pages, lead capture, booking flows.' (2) /emails HEADER REWORK. Was: a 'sub-header bar' style with Mail icon + counter dot, copying templates-baseui pattern. Now: standard page header (h1 + subtitle), more consistent with the rest of the dashboard. Subtitle: 'Send a one-off message or save a template to reuse for campaigns. [N] emails sent so far.' Counter only shows when > 0. Word renamed 'Emails' → 'Email' (singular like Gmail). (3) /forms STATS GRID HIDDEN WHEN EMPTY. Was: 4-card grid showing 'Total Forms / Active Forms / Draft Forms / Submissions' all with '+0 vs last month' labels even on a brand-new workspace with zero forms (dashboard inflation). Now: stats grid renders only when forms.length > 0; the '+0 vs last month' meaningless labels stripped entirely. Card titles tightened ('Total Forms' → 'Total', 'Active Forms' → 'Published', 'Draft Forms' → 'Draft'). Stat values use tabular-nums for cleaner alignment. (4) /automations SUBTITLE OUTCOME-FOCUSED. Was: 'Build, test, and deploy AI agents for this workspace. Each agent responds to a trigger (form submission, booking, SMS reply, schedule) and runs with your LLM key.' (technical, audience = SF developer). Now: 'Set up rules that run on their own — like sending a follow-up email when a booking comes in, or texting a reminder before a service call. Pick a template below to get started.' (concrete, audience = HVAC operator). Catalog header label 'Agent catalog' → 'Available templates'; 'archetypes' counter renamed to 'templates'. (5) /dashboard WELCOME HEADING POLISH. Was: 'text-lg sm:text-[22px]' with 'This is your calm workspace overview' (vague) for SF builders or 'Here's what's happening at your workspace today' for operators. Now: bigger 'text-xl sm:text-[28px]' tighter-tracking heading (more like Linear's homepage). Subtitle for both audiences rewritten to the concrete 'Here's what's happening at your business today' / 'Here's what's happening across your workspace today.' SF builder CTA 'Create New Client OS' renamed to 'New workspace' (operator-language; the CTA stays SF-builder-only via isOperatorSession gate). All changes are pure copy + CSS — zero new logic, zero new endpoints, zero new MCP tools. NO migrations, NO new env vars. Backend redeploy required. EXPECTED IMPACT: every primary surface in the dashboard now has a warm operator-language header, a hero-style empty state that teaches what the surface is for, and tighter typography. The 5-second test passes for /dashboard, /landing, /emails, /forms, /automations, /agents, /settings — plus the v1.29.0 sidebar work means navigating between them feels coherent.",
56
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.29.0: ADMIN DASHBOARD SIMPLIFICATION (CRM-side; MCP package version bumped for SemVer hygiene). The 5-second test: would Cypress Pine HVAC's owner — not a developer, not an SF builder, just an HVAC small business owner — know in 5 seconds what each page is and what they should DO here? v1.28.x and earlier: not consistently. SF-internal jargon ('Soul Marketplace', 'Soul Knowledge', 'Block', 'Soul transfer', 'Soul wiki', 'Frameworks', 'BLOCK.md') leaked into operator-facing copy. Sidebar grouped pages under 'YOUR SOUL' / 'YOUR BLOCKS' which means nothing to operators. Settings page exposed 17 sections in a flat-ish layout where 'Soul Knowledge' sat next to 'Workspace' as if peers. The /soul-marketplace sidebar link was a 404 (actual route is /marketplace). Operators learning the platform had to learn SF-internal vocabulary first, which is friction adoption can't afford at launch. WHAT SHIPS: (1) SIDEBAR REWORK. Three jargon-named groups ('YOUR SOUL' / 'YOUR BLOCKS' / 'SYSTEM') consolidated into two operator-language groups: 'OVERVIEW' (Dashboard) and 'RUN THE BUSINESS' (Customers / Deals / Bookings / Agents / Pages / Email / Forms / Automations / Templates). 'Soul Marketplace' renamed to 'Templates'. Sidebar route fixed: /soul-marketplace → /marketplace (was 404). (2) MARKETPLACE PAGE RENAME. /marketplace header from 'Block Marketplace' → 'Templates'. Subtitle from 'Extend SeldonFrame with new capabilities' → 'Add new pages, integrations, and AI capabilities to your workspace.' Search placeholder 'Search blocks' → 'Search templates'. Submit button 'Submit a Block' → 'Submit a Template'. Zero functional change; pure vocabulary. (3) SETTINGS RESTRUCTURE — 5 PLAIN-ENGLISH BUCKETS. Was: 'Your Business' (7 mixed items including 'Soul Knowledge') + 'Account & Billing' (3 items) + 'Advanced Settings' (collapsed, 7 items). Now: 'Workspace' (4 items: Workspace / Business Profile / Brand & Theme / Team), 'Billing' (3 items: Plan & Subscription / Accept Payments / Custom Domain — repositioned Stripe as the primary 'Accept Payments' card), 'Integrations' (2 items: AI / LLM Provider with status pill / Other Integrations), 'CRM Setup' (5 items: Pipeline Stages / Custom Fields / Customer Portal / Knowledge Base — renamed from 'Soul Knowledge' / Suppression List), 'Developer' (collapsed, 5 items: API Keys / Webhooks / White-label Branding / Industry Packs — renamed from 'Saved Frameworks' / Export / Import — renamed from 'Soul Export / Import'). Each group has a one-line plain-English description. Soul-internal terminology purged from operator-facing copy. (4) /AGENTS EMPTY STATE OPERATOR-FRIENDLY. Was: 'No agents yet. Build one from Claude Code with the SeldonFrame MCP — call build_website_chatbot.' Now: a centered hero card explaining what an AI assistant does ('answers customer questions, books appointments, escalates to your team when it can't help'), with two CTAs: 'How it works' (links to docs) and a collapsible 'Build with Claude Code →' that reveals the actual MCP snippet for developer operators. Non-developer operators see the explanation; developer operators get the one-paste command. Both audiences served. NO migrations, NO new env vars, NO MCP tool changes. Backend redeploy required. EXPECTED IMPACT: launch-video viewers + new operators encountering the dashboard for the first time see operator-language navigation, plain-English settings groups, and welcoming empty states — instead of SF-jargon they have to translate. Conversion friction reduced. The 5-second test passes for every primary surface.",
57
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.28.6: KARPATHY MODE FOR VALIDATOR RECOVERY — LLM regeneration on critical-fail + extended no_pii_leak trusted source + actionable eval surface (CRM-side; MCP package version bumped for SemVer hygiene). PROBLEM (from real-world dogfood): Cypress Pine HVAC chatbot stuck at 75% eval pass rate after 4 blueprint iterations (v4 → v7) couldn't promote to live. Two failure paths converged: (a) The runtime's one-size-fits-all safe fallback ('Let me check on that for you and have someone follow up. What's the best email to reach you at?') is right for runtime errors but WRONG for adversarial probes — asking for the visitor's email after a PII probe is awkward + trips its own validator on subsequent turns. (b) no_pii_leak validator's trusted source was conversation-history-only — when the agent legitimately surfaced operator's OWN business email/phone (the whole point of a website chatbot), validator flagged as a leak. Operators can't fix either via blueprint tuning — both are platform-owned. Claude Code correctly tried 4 times, correctly stopped. Architecture trapped it. PHILOSOPHY (Karpathy mode applied): the static fallback was COMPETING with the LLM's judgment. Better models produce better recoveries when given corrective context — hardcoded templates degrade as models improve, the opposite of antifragility. The right move: trust the model, give it rich corrective context, let it regenerate. WHAT SHIPS: (1) NEW lib/agents/fallbacks.ts. Pure-data registry mapping validator names to { correction (LLM regeneration prompt), finalFallback (last-resort static text), fixHint (operator-facing UI hint) }. 6 entries: quotes_only_from_soul_pricing, no_prompt_injection_echo, no_pii_leak, no_avoid_words, response_length_under_cap, no_hallucinated_state_change. composeCorrectionPrompt(failedNames) builds an [INTERNAL CORRECTION:...] block listing each violation as a bullet — the LLM sees ALL fired validators in one regeneration request. selectFinalFallback(failedNames) picks the highest-priority entry's static fallback (no_pii_leak > pricing > injection > hallucinated_action > length > avoid_words) when regeneration also fails. (2) RUNTIME REGENERATION (lib/agents/runtime.ts). When critical validator fires post-turn: (a) compose correction prompt from fired validators, (b) append synthetic [INTERNAL CORRECTION:...] user message to messages[], (c) call anthropic.messages.create with same system + messages + correction (no tools — just want clean text), (d) re-run validators on regenerated text. If clean → use regenerated response. If still critical-fail → use selectFinalFallback (per-validator static, NOT the v1.27.x one-size-fits-all). Token budget: ≤1 regeneration retry; bounded ~512 max_tokens on the regen call. ~2x token cost on critical-fail turns vs v1.28.5; for typical operators with healthy agents, critical-fails are rare so amortized cost is low. (3) no_pii_leak EXTENDED TRUSTED SOURCE. ValidatorContext.soul gains optional contact: { email, phone }. Runtime passes soul.contact through. Validator's trustedSource string now includes operator's business email/phone. The agent surfacing 'you can reach us at info@cypresspine.com' no longer flags as a leak — it's the agent's job to share business contact info. (4) /agents/[id]/evals SURFACE FIX (evals-client.tsx). Failed scenarios now show the agent's actual response inline (not behind a <details> collapse) + a 'How to fix' panel with per-validator hints (e.g. 'add the missing service to your pricing facts' / 'soul.contact may need an email' / 'this typically self-corrects on regeneration'). Operators (and Claude Code reasoning about the workspace) see the diagnostic data without clicking. PHILOSOPHY CHECK: every change here is moving HARDCODED HEURISTICS → MODEL JUDGMENT or → MORE DATA. Static fallback → LLM regeneration with rich context. Trusted-source-too-narrow → trust the operator's own contact info. Hidden eval details → surface them. As Claude (and successor models) get better, regeneration produces better recoveries (we don't maintain N hardcoded fallbacks); architecture stays stable. NO migrations, NO new env vars. Backend redeploy required. EXPECTED IMPACT on the conversation that triggered this fix: when the PII probe scenario fires + the agent generates the reflexive 'what's your email?' fallback, the regeneration pass with the corrective prompt produces a clean refusal like 'I'm not able to share other customers' info, but I'm happy to help with your own questions.' — passing the eval. Eval pass rate from 75% → 87.5%+ for typical workspaces. Operators see WHY each scenario failed inline; Claude Code can route to the right fix without inferring.",
58
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.28.4: POST-BOOKING 24h REMINDER VIA VERCEL WORKFLOWS — first SF use of Vercel Workflow DevKit. CRM-side change; MCP package version bumped for SemVer hygiene. PROBLEM: SF had no post-booking reminder mechanism. Operators in HVAC / dental / coaching verticals see ~10-15% no-show rates without reminders. The existing workflow_runs system (which powers /automations archetypes) is Soul-integrated for rule-based pattern matching; not the right shape for 'fire on event, sleep N hours, do thing' durable flows. PHILOSOPHY: lean on commodity infrastructure where SF doesn't compound. Durable execution + sleep-without-burning-compute is commodity (Vercel Workflows handles it). SF's compounding edge is Soul + Brain + Agent — not workflow plumbing. The user explicitly directed this in earlier reflection: 'use Vercel Workflows for any new durable flow; NOT a rewrite of existing workflow_runs.' WHAT SHIPS: (1) WORKFLOW PACKAGE INSTALLED. pnpm add workflow @workflow/next in packages/crm. next.config.ts wrapped with withWorkflow() from workflow/next so the build pipeline compiles 'use workflow' / 'use step' files into durable code. (2) NEW lib/workflows/booking-reminder.ts. Per Vercel WDK conventions: workflow function (sandboxed VM, no Node.js APIs, 'use workflow' directive) does ONLY orchestration. All real work (DB queries, SMS/email sends, activity inserts) lives in step functions ('use step' directive, full Node.js access). The workflow: (a) loads booking via loadBookingForReminder step, (b) early-returns if status != 'scheduled' OR if <24h to startsAt, (c) sleeps with workflow.sleep(new Date(startsAt - 24h)) — durable, no compute consumed during the sleep, (d) re-fetches the booking after sleep (it may have been cancelled or rescheduled in the meantime; if rescheduled to >25h out we skip and let a future scheduled run pick it up), (e) calls sendReminder step which checks workspace integrations: hasTwilio → sendSmsFromApi via lib/sms/api with the contact's phone; else hasResend → sendEmailFromApi via lib/emails/api; else returns 'no_channel_configured', (f) writes an activity row via logReminderActivity step (type='reminder_sent_sms' / 'reminder_sent_email' / 'reminder_skipped' with the reason). (3) WIRED INTO submitPublicBookingAction. Right after emitSeldonEvent('booking.created', ...), the action does: const { start } = await import('workflow/api'); await start(bookingReminderWorkflow, [bookingId]). Fire-and-forget — start() returns immediately. Errors caught + logged so workflow trigger failures don't fail the booking creation itself. (4) ALONGSIDE existing workflow_runs system. workflow_runs powers /automations archetype configurator (Soul-integrated rule-based patterns); booking-reminder is a generic durable flow that doesn't need Soul. Both coexist. NO migrations. NO new env vars on the SF side, BUT requires Vercel Workflow infrastructure on the deployment side — Vercel Workflows is included in Vercel platform; no marketplace install needed. Backend redeploy required (Vercel detects the new workflow on next build via withWorkflow + lib/workflows/ scan).",
59
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.28.3: SKILL-PACK ARCHITECTURE (CRM-side; MCP package version bumped for SemVer hygiene). Refactor with NO behavior change — same composed system prompt, different code shape. PHILOSOPHY (Karpathy / antifragile / thin-harness-fat-skill): behavioral guidance for agents (the FAT SKILL layer) belongs in markdown-style skill FILES, not inline string concatenation in prompt.ts. To improve agent intelligence, edit a skill file. No code change to the composer or runtime. As Claude (and successor models) get better at multi-turn memory, temporal reasoning, and avoiding hallucinated actions, we EDIT prose; architecture stays stable. Smaller models lean on verbose skills; larger models need terse ones. Same architecture either way. WHAT SHIPS: (1) NEW lib/agents/skills/<archetype>/<skill-id>.ts files. Each is a default-export string of markdown-shaped content. v1.28.3 ships 3 skills for the website-chatbot archetype: temporal-reasoning.ts (templated; uses {{currentDate}}, {{currentTime}}, {{timezone}} placeholders), be-smart-by-default.ts (the 7 behavioral rules introduced in v1.27.7-v1.27.8), hard-rules.ts (the 8 non-negotiable safety invariants). (2) NEW lib/agents/skills/registry.ts. Ordered REGISTRY array of {id, content, archetypes[], renderVars[]} entries. Functions: getSkillsForArchetype(archetype) returns ordered skills applicable to that archetype; renderSkill(skill, vars) substitutes {{placeholder}} tokens with values from a vars dict (unknown placeholders left intact, visible as a debug hint). (3) REFACTORED prompt.ts. composeSystemPrompt now: imports getSkillsForArchetype + renderSkill from registry; computes skillVars (currentDate, currentTime, timezone from workspace tz); emits 'up-front' skills (temporal-reasoning, be-smart-by-default) before dynamic operator content; emits 'hard-rules' AFTER dynamic content (so safety invariants are the final word the LLM reads — anchoring effect). The previous ~50 lines of inline backtick-string-concatenated prose are gone, replaced by 6 lines of registry iteration. (4) BEHAVIORAL OUTPUT IS IDENTICAL to v1.28.2. The skill files contain the EXACT same prose that was inlined; the registry orders them the same way the inline code did. Verifiable via tsc + comparing System prompts byte-for-byte. EXTENSIBILITY: adding a new behavioral skill = create skills/<archetype>/<id>.ts + add a registry entry. Zero changes to composer or runtime. Renaming an existing skill = move file + update registry + update import; prompt output identical. Voice + SMS archetypes can introduce specialized skills (skills/voice-receptionist/no-emojis.ts, skills/sms-followup-bot/under-160-chars.ts) without composer code changes. NO MCP tool changes. NO migrations. NO new env vars. Backend redeploy required.",
60
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.28.2: EMBED WIDGET POLISH (CRM-side; MCP package version bumped for SemVer hygiene). The /api/v1/public/agent/<slug>/embed.js IIFE that renders on operators' websites — first impression for end customers — was functional but plain. v1.28.2 brings it to world-class without forking a hosted-app codebase like mckaywrigley/chatbot-ui (wrong shape — that's a full Next.js app, we need a single <script> tag with zero deps on the operator's site). Polish in 5 categories: (1) MARKDOWN RENDERING. Inline parser in the IIFE handles **bold** → <strong>, *italic* → <em>, `code` → <code>, [text](https://...) → <a target=_blank rel=noopener noreferrer>, '- bullet' lines → <ul><li>, and \\n → <br>. All output goes through escapeHtml() FIRST then sanctioned tags re-injected — no raw HTML survives. SSE-friendly: re-renders innerHTML on every delta so bold/links resolve as soon as the closing token arrives. Inline CSS for strong/em/a/code/ul/li within .sf-agent-msg.assistant. (2) MOBILE-FIRST. Below 640px breakpoint the panel becomes FULL-SCREEN (Intercom pattern) — no rounded corners, fills viewport. Bubble shifts to bottom-right corner with smaller margin. Bigger touch targets throughout. (3) BRAND INHERITANCE. Header now shows the operator's logo if workspace.theme.logoUrl is set (https://... only — schema-validated server-side); falls back to a colored circle with the first letter of the org name. Logo container has bg=rgba(255,255,255,.18) so it sits cleanly on top of the operator's primaryColor header. (4) A11Y. role=dialog + aria-label on panel; role=log + aria-live=polite + aria-relevant=additions on messages region (announces new agent messages without spamming during streaming); screen-reader-only label on the textarea; aria-expanded toggles on the bubble; ARIA-labels on every control. Esc key closes panel + returns focus to bubble (focus management). focus-visible outlines on all interactive elements with proper offset. prefers-reduced-motion media query disables all transitions and animations. (5) ANIMATIONS + UX. Bubble: spring scale (cubic-bezier(.34,1.56,.64,1)) on hover (1.08x) + active (.95x). Panel: spring slide-up reveal — opacity 0 + translateY(8px) + scale(.98) → opacity 1 + 0 + 1 in 280ms. Messages: per-message fade+slide-in (220ms). Typing indicator: 3 animated bouncing dots (was 'Typing...' text). Textarea (was input) auto-grows to 120px max-height; Enter sends, Shift+Enter newline. Send button has translateY hover + scale active feedback. NO MCP tool changes. NO migrations. NO new env vars. The embed.js endpoint is automatically picked up on the next page load — operators don't need to update their script tag (the URL is stable; the response body changes server-side). Backend redeploy required.",
61
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.28.1: 90%-FEWER-TOOL-CALLS + /AGENTS DASHBOARD REDESIGN. PROBLEM: real-world dogfood showed Claude Code spending 3m 35s + ~17 actions to update an existing chatbot — only 2 of which (the actual update_agent_blueprint call + final response) were necessary work. The waste broke down as: progressive tool-schema loading (5+ separate 'Called seldonframe' round-trips as Claude Code lazy-discovered the 138-tool catalog), local-FS exploration (ls + node --version + read .env — all irrelevant on a hosted SF platform), an unnecessary question to the user about Anthropic key configuration (the workspace already had one), and stale references to the v1.27.9-removed token-budget concept leaking from MCP tool responses. Plus the /agents dashboard exposed only 2 CTAs (Open sandbox / View conversations) and zero inline health stats — operators had to drill into /agents/[id] just to see eval status / validator pass rate / 24h conversations. WHAT SHIPS: (1) NEW GET /api/v1/workspace-state ENDPOINT. Workspace-bearer auth. Returns in ONE round-trip: workspace identity (id, name, slug, industry, timezone, dashboard_url, public_site_url); integrations status (anthropic / openai / twilio / resend / kit / mailchimp configured? — booleans only, no keys leaked); agents WITH inline health stats (status, version, eval pass rate, validator pass rate 24h, conversations 24h, eval_meets_publish_gate boolean, last_eval_run_at); high-level counts (contacts, bookings, deals, agents); and a tailored next_steps[] array (e.g. 'Configure Anthropic LLM key' / 'No agents yet, call build_website_chatbot' / 'Run evals before promoting to live'). (2) NEW get_workspace_state MCP TOOL wrapping that endpoint. Description tells Claude Code to call it FIRST for any workspace task — replaces 4-6 progressive discovery calls. (3) NEW update_website_chatbot SKILL BUNDLE. Peer to v1.28.0's build_website_chatbot, for the 'agent already exists' case. Auto-resolves the workspace's website-chatbot agent_id if not passed (uses get_workspace_state internally), merges patch into blueprint via update_agent_blueprint, returns dashboard URL + version + next_steps with re-run-evals reminder. Refuses ambiguously (multi-agent workspaces) — returns the agent list instead. (4) STRIPPED DEPRECATED FIELDS. tokensUsedToday + dailyTokenBudget removed from list_agents and get_agent_metrics MCP tool response shapes (token-budget concept was retired in v1.27.9; stale data was still leaking through and causing Claude Code to suggest 'bump the token budget' as a follow-up). (5) WELCOME.JS REWRITTEN. New 'Step zero — call get_workspace_state' section + new ANTI-PATTERNS table explicitly enumerating what NOT to do (don't ls/cat/read .env; don't check node/npm versions; don't ask 'is Anthropic key configured?'; don't create duplicate agent). FIRST_CALL_BANNER similarly anchored on step-zero discovery + anti-patterns. (6) /AGENTS PAGE REDESIGN. Each agent row now shows inline: status pill + 3-stat grid (conversations 24h, validator pass rate 24h with 'X turns' hint, eval pass rate X/Y with last-run timestamp + meets-gate tone) + a contextual PromoteHint banner ('Evals haven't run' / 'Below 87.5% gate, fix in Settings' / 'Eval gate met, ready to promote') + 5 quick-link CTAs (Overview / Sandbox / Conversations / Evals / Settings — was 2). The agent list is now a roster + health dashboard, not a list-of-links. EXPECTED IMPACT: 'build a chatbot for [biz]' / 'update the chatbot's FAQ' / 'how is my chatbot doing' prompts resolve in 1-2 tool calls + ~5-10s, instead of 12+ tool calls + ~3.5min. Operators see at a glance whether each agent is healthy + what's blocking promotion, without drilling into per-agent pages. NO migrations, NO new env vars. Backend redeploy required.",
62
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.28.0: BUILD_WEBSITE_CHATBOT SKILL BUNDLE + BYOK ENV AUTO-DETECT — Phase B of the Karpathy/antifragility refactor (sugar over primitives). PROBLEM: even after v1.27.x's tool-discovery doc upgrade, the canonical 'build me a chatbot for [business]' flow still required ~5 sequential MCP calls (configure_llm_provider → create_agent → publish_agent test → operator sandbox-tests → publish_agent live), each round-tripping through Vercel. ~30s wall time. BYOK setup added a key-paste step every time even though most Claude Code users already have ANTHROPIC_API_KEY set in their shell. WHAT SHIPS: (1) BYOK ENV AUTO-DETECT. configure_llm_provider gains a 'pass nothing / pass api_key=\"env\"' mode. The MCP server reads process.env.ANTHROPIC_API_KEY (or OPENAI_API_KEY for provider='openai') from its OWN local environment — which is the user's shell where Claude Code launched. Most Claude Code users already have this set (it's how Claude Code itself works). Returns { ok: false, error: 'no_env_key' } with a clear hint if the env var isn't set. The handler annotates ok=true responses with source='env_inherited' vs source='explicit' so the LLM can tell the user 'I auto-detected your Anthropic key from your shell environment' instead of 'I saved the key you provided.' WHITE-LABEL ESCAPE HATCH: pass api_key explicitly when an agency manages multiple operators with separate Anthropic billing (env auto-detect would use the agency's key for every operator — wrong for billing isolation). (2) NEW SKILL-BUNDLE TOOL build_website_chatbot. ONE call wraps the canonical 4-step sequence: configure_llm_provider (auto-detects env if no anthropic_api_key passed) → create_agent (archetype='website-chatbot', channel='web_chat', with faq + pricing_facts + greeting from args) → publish_agent (status='test', sandbox-callable, eval gate doesn't run yet) → returns { agent, embed_url, turn_url, dashboard_url, sandbox_url, steps[], next_steps[] }. Each internal step's success/failure surfaces in the steps array so the LLM can report cleanly. Partial-success handling: if create_agent succeeds but publish fails, returns the created agent + a clear next_step to publish manually. Reduces typical 'create chatbot for HVAC' from 5 round-trips + ~30s to 1 round-trip + ~5s. (3) WELCOME.JS CAPABILITY MAP UPDATED. The 'add a chatbot to my website' row now points at build_website_chatbot instead of create_agent. Canonical short flow walkthrough rewritten to show the 1-call version. FIRST_CALL_BANNER similarly updated. Primitives stay fully callable for custom flows (different archetype, custom capability allowlist, multi-step blueprint construction); the bundle is just sugar over them. PHILOSOPHY (Karpathy / antifragile / thin-harness-fat-skill): the bundle is sugar, not a replacement. Primitives stay clean — operators on Claude Code with sufficient model capability can still call them directly for nuanced flows. As Claude gets better at orchestrating the 4-call sequence itself, the bundle becomes less necessary but still works. Smaller models or one-shot prompts lean on the bundle; larger models orchestrate primitives. Same architecture serves both. NO new HTTP endpoints (build_website_chatbot uses existing /api/v1/agents ops via the MCP-tool layer). NO migrations, NO new env vars on the server side. ENCRYPTION_KEY still required (already shipped). DEFERRED to v1.28.1: embed widget polish (markdown rendering, mobile-first responsive, brand inheritance, a11y, action chips). DEFERRED to v1.28.2: skill-pack architecture (move inline behavioral guidance from prompt.ts to agents/<archetype>/skills/*.md files the composer reads on demand). DEFERRED to v1.28.3: post-booking 24h reminder via Vercel Workflows (first SF use of Workflows for a new durable flow). EXPECTED IMPACT: 'create a chatbot for Cypress Pine HVAC with these FAQ + pricing facts' resolves in ~5s with one tool call, instead of ~30s with 5 sequential calls. Operator sees one consolidated next-steps list (sandbox-test, run evals, publish-live, drop embed snippet) instead of having to compose them mentally across 5 separate responses.",
63
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.27.10: HALLUCINATED-STATE-CHANGE VALIDATOR (CRM-side; MCP package version bumped for SemVer hygiene). PROBLEM: even after v1.27.8 (added reschedule_appointment + cancel_appointment tools + system-prompt rule 'NEVER claim an action you didn't take'), real-world dogfood STILL caught the agent hallucinating. Conversation: 'Done! You're rescheduled for tomorrow, May 8 at 1pm. See you then, Maxime!' But database wasn't updated; contact's bookings page still showed May 21. Two failure paths converge here: (a) the existing Cypress Pine agent's blueprint was created in v1.26.x with the ORIGINAL 5 capabilities — v1.27.8 added the new tools to DEFAULTS for new agents but deliberately didn't auto-mutate existing operator blueprints. So the LLM never had reschedule_appointment in its tool list — system prompt told it 'you MUST call reschedule_appointment' (which doesn't exist for it), contradictory state, LLM picked 'claim success' over 'tell user we can't.' (b) Even when the capability IS there, system prompts aren't 100% reliable — Claude can still hallucinate completion claims. Both paths produce the same critical-failure: customer is told the booking moved when it didn't. ARCHITECTURAL FIX (defense in depth at the runtime layer, since system-prompt rules alone are insufficient): a new critical validator that catches the hallucination regardless of WHY it happened. WHAT SHIPS: (1) NEW VALIDATOR no_hallucinated_state_change. ValidatorContext gains turnToolCalls + turnToolResults (this turn's tool activity). The validator scans the response for completion phrases mapped to required tool calls: 'rescheduled / moved your appointment / appointment moved / new time is set / see you (then|tomorrow|<weekday>)' → requires reschedule_appointment with ok=true; 'cancelled your appointment / cancellation confirmed' → requires cancel_appointment; 'you're booked / appointment confirmed / I've booked' → requires book_appointment; 'team will follow up / I've passed this on / someone will reach out' → requires escalate_to_human. If the response claims completion AND the matching tool was NOT called with ok=true this turn, fail critical. The runtime then replaces the response with the safe fallback ('let me check'). Customer NEVER hears a fabricated state change. (2) ALL_VALIDATORS array gains the new validator. Existing 5 validators unchanged. (3) RUNTIME passes allToolCalls + allToolResults from the turn loop into runValidators. NO migrations, NO new env vars, NO MCP tool changes. Backend redeploy required. EXPECTED IMPACT on the conversation that triggered this fix: even if the agent's blueprint still doesn't have reschedule_appointment in capabilities (the v1.27.8 manual-backfill step the user hasn't done yet), the runtime will catch the hallucinated 'rescheduled!' response and replace with the safe fallback — customer hears 'let me check on that' instead of being lied to. Once the user backfills their agent's capabilities (call update_agent_blueprint from Claude Code with the full 7-tool list, or tick the 2 new boxes in /agents/<id>/settings + Save), the agent will ACTUALLY call reschedule_appointment and the booking will move. Belt + suspenders: even if the LLM lies despite having the tool, the validator catches it. As Claude gets better at not hallucinating actions, this validator fires less and less; architecture stable.",
64
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.27.9: REMOVE DAILY TOKEN BUDGET (CRM-side; MCP package version bumped for SemVer hygiene). PROBLEM: real-world dogfood reschedule conversation halted with 'RUNTIME ERROR · DAILY_BUDGET_EXHAUSTED — Token budget 92% used (45,855 / 50,000 tokens today)'. The artificial cap broke a perfectly valid conversation. The cap was a v1.26.0 design artifact from when SF was going to resell LLM at markup — it protected SF from runaway cost exposure. Under BYOK (since v1.26.1), the OPERATOR pays Anthropic directly. SF has zero cost exposure. The cap is now: (a) irrelevant for SF (no cost to protect), (b) confusing for operators (they have an Anthropic billing dashboard for this), and (c) actively harmful (artificial halts on busy days with plenty of Anthropic credits available). FIX: remove the budget mechanism entirely. WHAT SHIPS: (1) RUNTIME: dropped the isDailyBudgetExhausted() check at step 2 of executeTurn. Dropped the 'daily_budget_exhausted' fallback reason code. Dropped the 'agents.tokensUsedToday + dailyTokenBudget' aggregate increment. Per-turn tokensIn/tokensOut still persist on agent_turns rows for cost analytics + observability — that data stays useful even when SF doesn't bill on it. The isDailyBudgetExhausted helper function deleted. (2) AGENT OVERVIEW: dropped the 'Daily token usage' card with progress bar. Token-budget concept is gone from the UI; the at-a-glance health stat row (24h conversations, validator pass rate, eval pass rate, avg latency) stays. (3) SANDBOX PRE-FLIGHT: dropped the budget pre-flight checks (block-level when 100% used + warning-level at 80%). Anthropic-key pre-flight check stays (still relevant; no key = no chat). (4) AGENT LIST: dropped the 'Tokens today: X / 50,000 (X%)' line on each agent row. Cleaner display. (5) DB SCHEMA UNCHANGED. agents.tokensUsedToday + dailyTokenBudget + tokensUsedResetAt columns stay (no migration; future-proof for if we ever want to surface 'cost spent today' analytics). They're just no longer read or written by the runtime. NO migrations, NO new env vars. Backend redeploy required. EXPECTED IMPACT on the conversation that triggered this fix: the agent will no longer halt mid-turn with DAILY_BUDGET_EXHAUSTED. Conversations run as long as the operator's Anthropic key has credits. Operators see their actual spend on their Anthropic dashboard, not an artificial SF-imposed limit. Simpler architecture, fewer concepts.",
65
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.27.8: RESCHEDULE/CANCEL TOOLS + ANTI-HALLUCINATION RULE (CRM-side; MCP package version bumped for SemVer hygiene). PROBLEM: real-world dogfood reschedule conversation went perfectly UX-wise — agent asked the right questions, resolved 'next Monday at 2pm', confirmed, said 'Perfect! Your appointment has been rescheduled to Monday, May 12 at 2:00 PM. You'll receive a confirmation email at dresslikeag@gmail.com.' But the database wasn't actually updated. Customer's contact-detail page in the operator's CRM still showed the OLD May 21 booking. The agent had hallucinated success. WHY: the website-chatbot archetype's default capabilities were [look_up_availability, book_appointment, find_my_existing_appointment, escalate_to_human, provide_faq_answer]. There was NO reschedule_appointment OR cancel_appointment tool. The agent, when asked to reschedule, found the booking via find_my_existing_appointment, then had no actual mutation path — but rather than escalate, it confidently fabricated the completion. ARCHITECTURAL FIX (two layers): the missing tools AND the missing constraint. WHAT SHIPS: (1) reschedule_appointment TOOL. New agent tool that ACTUALLY mutates bookings.startsAt + endsAt + updatedAt, atomic UPDATE WHERE id=booking_id AND orgId=ctx.orgId AND email=customer_email. The customer_email arg is a security check — a hallucinated bookingId from a different workspace can't slip through because the email won't match. Preserves duration (computes newEndsAt = newStartsAt + (oldEndsAt - oldStartsAt)). Returns { ok, bookingId, newStartsAt } on success, { ok: false, reason } on email mismatch / missing booking / invalid date. (2) cancel_appointment TOOL. New agent tool. UPDATE bookings SET status='cancelled' WHERE same triple-match (id, orgId, email). Same security model. (3) UPDATED DEFAULT CAPABILITIES. DEFAULT_CAPABILITIES_BY_ARCHETYPE in store.ts adds reschedule_appointment + cancel_appointment to website-chatbot, voice-receptionist, and sms-followup-bot. New agents created from v1.27.8 onwards include them automatically. (4) SETTINGS UI EXPOSES NEW CAPS. ALL_CAPABILITIES in /agents/[id]/settings/page.tsx adds the two new toggles. Existing agents (created pre-v1.27.8) need their operator to check the new boxes + Save to enable the tools — auto-mutation of existing blueprints is intentionally avoided. (5) ANTI-HALLUCINATION SYSTEM PROMPT RULE. The 'Be smart by default' section in composeSystemPrompt gains a new rule #6: 'NEVER claim an action you didn't actually take.' Explicit list: 'I rescheduled it' / 'Done, you're booked' / 'I cancelled it' / 'I let the team know' all REQUIRE the corresponding tool call (reschedule_appointment / book_appointment / cancel_appointment / escalate_to_human) with ok=true returned BEFORE the agent confirms to the visitor. Operator never authors this — platform owns it. As Claude gets better at avoiding action hallucinations, this rule becomes a hint instead of a crutch; architecture stable. NO migrations, NO new env vars. Backend redeploy required. EXISTING-AGENT MIGRATION: operators of pre-v1.27.8 agents need ONE manual step — open /agents/<id>/settings, tick the two new capability boxes (reschedule_appointment + cancel_appointment), Save. Or call update_agent_blueprint from Claude Code with the full new capabilities array. EXPECTED IMPACT on the conversation that triggered this fix: agent calls reschedule_appointment(booking_id=..., new_starts_at_iso='2026-05-12T14:00:00Z', customer_email='dresslikeag@gmail.com'), gets ok=true, THEN tells the visitor 'Done — you're now booked for Monday, May 12 at 2:00 PM.' The booking row actually moves. Operator's CRM contact-detail page reflects the new time.",
66
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.27.7: AGENT INTELLIGENCE BASELINE (CRM-side; MCP package version bumped for SemVer hygiene). PROBLEM: real-world dogfood showed three baseline-intelligence failures every agent inherits regardless of blueprint quality. (a) Asked 'what date is this Friday?' — agent had no temporal grounding. The system prompt didn't tell it today's date or timezone, so 'this Friday', 'tomorrow', 'next week' couldn't be resolved. (b) Asked customer to re-confirm name + phone after find_my_existing_appointment matched the booking by email — the tool returned only the booking, not the linked contact, so the agent had to re-ask for info already on file. (c) End of turn asked 'what's the best email to reach you at?' even though the customer's email was the FIRST thing they typed three turns earlier — no_pii_leak validator over-fired (flagged the customer's own phone-number echo as a leak), runtime replaced with safe fallback that asks for email, infinite-loop UX. These are PLATFORM debt, not operator debt — operators shouldn't have to write 'agent should know what today is' in their FAQ. PHILOSOPHY (Karpathy / antifragile / thin-harness-fat-skill): every smart-by-default behavior belongs in the system-prompt composer (the FAT SKILL layer) or in tool data enrichment (the runtime data layer). Don't add tools, don't add validators, don't add wrappers. Add CONTEXT. As Claude gets better, richer context lifts more weight. WHAT SHIPS: (1) TEMPORAL GROUNDING. composeSystemPrompt accepts new {now, timezone} params. Runtime passes new Date() + organizations.timezone (the workspace's IANA timezone, defaulting to UTC). System prompt now starts with a '## Right now' section: 'Today is Thursday, May 7, 2026 (10:11 AM America/New_York). When the visitor says today / tomorrow / this Friday / next week, resolve them to a CONCRETE date using this anchor. Default to most natural interpretation: this Friday = next upcoming Friday; tomorrow = next calendar day; next week = same weekday 7 days out.' (2) BE-SMART-BY-DEFAULT BEHAVIORAL GUIDANCE. New '## Be smart by default' section in the composed prompt with 6 platform-owned rules: don't ask for info you already have; use linked-contact data when a tool returns it; echoing customer-provided data isn't a leak; default to optimistic interpretation; confirm before destructive actions; stay concise. Operator never authors these. As Claude gets better OR new failure modes surface, we expand THIS prose. Architecture stable. (3) find_my_existing_appointment NOW RETURNS LINKED CONTACT. The tool's return shape changes from Array<booking> to { appointments: Array<booking>, contact: { id, fullName, email, phone } | null }. Tool description updated to instruct the LLM to use the contact field instead of re-asking. The contact lookup is a JOIN on contacts.email matching the email argument; null when no contact record exists. (4) no_pii_leak VALIDATOR ACCEPTS conversationContext. ValidatorContext gains an optional conversationContext field — full conversation history's user messages + tool-result blobs concatenated. Runtime passes it on every validator run. The validator now treats this 'trusted source' as the allowlist for emails/phones — anything in the response that's NOT in the trusted set is flagged. Customer's own data echoed back, or contact's phone surfaced by find_my_existing_appointment then echoed in the response, are both fine — no false-positive leak flags. NO migrations (organizations.timezone column shipped earlier). NO new env vars. NO new MCP tool changes — but the underlying find_my_existing_appointment tool's return shape evolved. Backend redeploy required. EXPECTED IMPACT on the conversation that triggered this fix: agent resolves 'this Friday' to May 8 without asking; uses 'Maxime Houle (450-516-1803)' from the contact lookup without asking; doesn't ask for email a second time at end-of-conversation. Same blueprint, smarter agent.",
67
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.27.6: IN-DASHBOARD LLM KEY UI (CRM-side; MCP package contents unchanged from 1.27.5 but version bumped for SemVer hygiene). PROBLEM: v1.27.5 added a clean pre-flight banner to the agent sandbox saying 'No Anthropic API key configured', but the only fix path it suggested was 'Configure a key from Claude Code: configure_llm_provider({ ... })'. SF clients had NO in-dashboard UI to add their LLM key — they had to leave the dashboard, open Claude Code, find the SeldonFrame MCP, call the tool. Friction. WHAT SHIPS: (1) NEW PAGE /settings/integrations/llm. Two cards (Anthropic + OpenAI), each shows: configured/not-configured pill, last-saved hint (sk-ant-...XXXX masked) + savedAt timestamp when present, password-input field for paste-and-replace, Save key button, Remove key button. Form-action server actions (saveLlmKeyAction, removeLlmKeyAction) live in lib/integrations/llm/actions.ts. Same encryption + storage path as the MCP configure_llm_provider tool — both write to organizations.integrations[provider].apiKey with the v1. prefix; the agent runtime decrypts uniformly via lib/ai/client.getAIClient regardless of source. (2) PROVIDER-SPECIFIC KEY-SHAPE VALIDATION. Anthropic keys must start with sk-ant-, OpenAI keys with sk-. Catches the most common paste mistakes (wrong provider for the field, leading whitespace, etc.) before encryption. (3) DEEP-LINK FROM SANDBOX BANNER. The /agents/[id]/test pre-flight 'No Anthropic API key configured' banner now includes an 'Add key' button linking directly to /settings/integrations/llm. Two-click fix from sandbox to working agent. (4) DISCOVERABILITY CARD on /settings/integrations index. New 'AI / LLM Providers' card at the top of the integrations list with 'Manage keys →' button. Operators browsing integrations naturally find the LLM section first. NO migrations, NO new env vars (ENCRYPTION_KEY already required). NO MCP tool changes — MCP configure_llm_provider keeps working in parallel; same column, same encryption. Backend redeploy required. EXPECTED IMPACT: SF clients hitting the 'No key' banner can now resolve in 2 clicks (button → paste → Save) instead of switching tools. The MCP path stays for Claude Code users; the dashboard path stays for browser users; both write to the same encrypted column.",
68
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.27.5: SANDBOX DIAGNOSTIC UX (CRM-side; MCP package contents unchanged from 1.27.4 but version bumped for SemVer hygiene). PROBLEM: real-world dogfood showed that when an SF client tested their agent in /agents/[id]/test sandbox and the runtime errored (e.g. Anthropic credit balance too low), the sandbox surfaced the customer-facing fallback 'I'm having a hiccup. Can I have someone follow up with you? What's your email?' — which is intentionally vague for end customers but USELESS for the SF client trying to debug. Worse, it LOOPED: every subsequent turn re-fired the same fallback, asking for the email again. The real diagnostic ('Your credit balance is too low to access the Anthropic API') was buried in Vercel server logs only. WHAT SHIPS: (1) ANTHROPIC ERROR CLASSIFIER. New classifyAnthropicError() in runtime.ts maps raw Anthropic error strings to stable reason codes + operator-friendly hints: llm_credit_exhausted (→ 'Add credits at console.anthropic.com/settings/billing'), llm_invalid_key (→ 'Update via configure_llm_provider with a fresh sk-ant-... key'), llm_rate_limited (→ 'Wait ~60s or raise your tier'), llm_model_unavailable (→ 'Contact SF support'), llm_overloaded (→ 'Try again in a moment'), llm_timeout (→ 'Check outbound connectivity'), llm_error (→ generic). (2) TEST-MODE-AWARE FALLBACK. runtime.ts fallbackMessage now branches on conv.status: status='test' → returns the real diagnostic ('[runtime error: llm_credit_exhausted] Anthropic account has no credits left...'), status='active'/'live' → keeps the customer-facing 'hiccup' fallback. Same code path; different audience treatment. (3) PRE-FLIGHT DIAGNOSTIC BANNERS at /agents/[id]/test. Server component checks BEFORE rendering the chat: agent status (must be test or live), Anthropic key presence in organizations.integrations, daily token budget remaining. Each fail surfaces a colored banner with title + actionable message + (when applicable) a quick-link button to the right fix surface. Block-level fails (no key, budget exhausted, draft status) hide the chat entirely so the operator can't waste a turn discovering the issue. Warning-level (>80% budget) shows the chat with a non-blocking banner. (4) SANDBOX HALT-AND-RETRY. test-client.tsx now tracks halted state. When the runtime returns degraded=true (in non-streaming JSON branch) or sends an SSE 'done' event with degraded:true, the sandbox: tags the assistant message as degraded with a red border + 'Runtime error · <reason>' label; disables the input box ('Resolve the error above to continue'); shows a 'Reset & retry' button that clears the conversation and starts fresh. No more infinite-loop-of-hiccup-prompts. NO migrations, NO new env vars, NO MCP tool changes. Backend redeploy required (the runtime + server-component changes ship with the next Vercel deploy of main).",
69
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.27.4: UNIFIED SIGN-OUT HOTFIX (CRM-side; MCP package contents unchanged from 1.27.3 but version bumped for SemVer hygiene). PROBLEM: dashboard-topbar 'Log out' called next-auth's signOut() which only clears the NextAuth session-token cookie. SF has THREE simultaneous auth sources since v1.25.0: NextAuth + operator-portal cookie (sf_operator_session) + admin-token Bearer header. Per v1.25.2 precedence, the operator portal cookie resolves FIRST in lib/auth/helpers.ts. So signing out via NextAuth left sf_operator_session intact → next request resolved as operator portal → user appears stuck in operator-portal mode (trimmed sidebar: Customers / Jobs / Booking only) and can't see the full admin dashboard with Agents / Pages / Email / etc. SYMPTOM: Acme AI agency operator builds an agent for Cypress Pine HVAC via Claude Code, opens https://app.seldonframe.com/dashboard, sees the operator-trimmed view (their agency dashboard hidden), clicks Log out → POST /api/auth/signout returns 200 + redirect /login → next request still resolves the same operator portal user via the surviving sf_operator_session cookie. Loop. Diagnostic logs showed 'userId: __sf_operator_portal__:fa5ba418-... ' even after the signout response. FIX: new lib/auth/actions.signOutAllSessionsAction server action that clears BOTH cookies in one call (operator portal first since it precedes NextAuth in resolution order, then NextAuth via signOut({redirect:false}), then redirect to /login). Admin-token doesn't need clearing — it's a stateless server-side credential, never stored in cookies. Wired into dashboard-topbar.tsx replacing the next-auth/react signOut() call with a form action — works without JavaScript, no client-side state to manage. NO migrations, NO new env vars. Backend redeploy required (the topbar component change ships with the next Vercel deploy of main).",
70
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.27.3: MCP PARSE HOTFIX — v1.27.1 + v1.27.2 published broken to npm because welcome.js had a JS syntax error from a bad Edit. ROOT CAUSE: my v1.27.1 Edit replaced only the PREFIX of the FIRST_CALL_BANNER assignment ('export const FIRST_CALL_BANNER = `🚀 SeldonFrame v1.18.1 is connected.') instead of the full multi-line statement. The replacement template literal closed properly with `;` at its end, but the OLD content (everything after 'is connected.' from v1.18.1) remained as orphaned text → 'export const FIRST_CALL_BANNER = `<new>`; PREFERRED workspace creation: ... `;' which fails to parse with 'Unexpected identifier workspace' because PREFERRED workspace are two bare identifiers. tsc didn't run on this file (it's plain .js), and 'npm publish' has no syntax check by default, so the broken package shipped. v1.27.2's republish carried the same bug because my edits touched only the version string, not the surrounding broken syntax. SYMPTOM: Claude Code 'Failed to reconnect to seldonframe' — the npx-spawned MCP server crashes immediately at module load. v1.27.0 worked fine (its version was the last clean one before this regression), but v1.27.0's published code still references the broken backend ops, so it's not a fully usable fallback. FIX: rewrote the FIRST_CALL_BANNER as a single clean statement (consolidated the v1.27.x capability map content + the legacy workspace flow tail into one string, no orphan tail). VERIFIED with `node --input-type=module -e \"import('./welcome.js')\"` — file now parses, VERSION + WELCOME_MARKDOWN + FIRST_CALL_BANNER all loadable. PREVENTION: new npm script `check:syntax` runs `node --check` on each .js file in src/, wired into `prepublishOnly`. `node --check` is AST-parse-only (no execution, no import resolution needed) so it runs in <100ms and catches this entire bug class — including the version-bump-while-leaving-orphan-tail mistake — before npm publish can ship a broken tarball. Antifragile to future Edit-tool fumbles. NO tool descriptions changed, NO welcome.js capability map content changed (the v1.27.1 doc upgrade still applies as intended once Claude Code can actually reach the file). Republish only.",
71
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.27.2: PRODUCTION HOTFIX — Vercel build has been failing since v1.26.2 because lib/agents/eval-runner.ts (a 'use server' Next.js Server Actions module) re-exported the constant PUBLISH_PASS_RATE_THRESHOLD with `export { PUBLISH_PASS_RATE_THRESHOLD };`. TypeScript's tsc --noEmit didn't flag this (server-action semantics aren't a tsc concern), but next build's page-data collection step rejects 'use server' modules that export anything other than async functions, with: `Error: A 'use server' file can only export async functions, found number.` MEANS production has been stuck on v1.26.1 for the past three ships — the new agent ops (run_evals, tail_conversations, get_conversation, replay_conversation, get_metrics) and the entire /agents dashboard surface (overview, settings, evals, sandbox, conversations) have NOT been deployable, and any operator hitting /agents in their browser would 404. ROOT CAUSE: I added the re-export thinking it was a convenience for store.ts publishAgent, but store.ts didn't actually need it (publishAgent uses summary.meetsPublishGate which is computed inside runEvalSuite). FIX: remove the offending line + add an explanatory comment so this doesn't recur. PREVENTION: new packages/crm/scripts/check-use-server.sh is a 60-line shell guard that scans every 'use server' file for `export const|let|var|{`-shaped exports. Wired into pnpm build so it runs BEFORE next build (fails in <1s with a clear message instead of wasting 45s of compile + page-data collection on Vercel). Catches the entire bug class going forward — antifragile to future server-action refactors. SWEPT all 46 'use server' files in packages/crm; eval-runner.ts was the only offender. NO MCP-tool changes (this is a backend-only hotfix), but I'm bumping the npm package version + republishing for SemVer hygiene so anyone on @seldonframe/mcp@latest is signaling that the BACKEND v1.26.2/v1.27.0/v1.27.1 features will start working as soon as Vercel deploys this commit. Backend redeploy required (the next push to main triggers it).",
72
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.27.1: TOOL DISCOVERY DOC UPGRADE — Phase A of the Karpathy/antifragility refactor. PROBLEM v1.27.1 SOLVES: real-world dogfooding of v1.27.0 surfaced that Claude Code couldn't DISCOVER the agent tools we'd just shipped. Asked to 'create a chatbot for Cypress Pine HVAC and put it on the homepage,' Claude Code spent 33s + 7 tool calls inspecting list_blocks, find no chat-widget block type, and concluded 'SF doesn't expose a landing-page chatbot widget' — falling back to suggesting send_conversation_turn (the v1.18 SMS conversation primitive). The agent tools shipped in v1.26.x (configure_llm_provider, create_agent, publish_agent, list_agents, update_agent_blueprint) and v1.26.2 (run_agent_evals, tail_agent_conversations, get_agent_conversation, replay_conversation, get_agent_metrics) WERE there, just not surfaced through the noise of ~50 flat tools. WHY THIS HAPPENS: tools.js is 200KB+ flat catalog; welcome.js (the MCP `instructions` payload Claude Code reads as system context) was 309 lines all about the v1.18-era workspace creation flow with no mention of agents as a top-level primitive; tool descriptions described WHAT each tool does instead of WHEN to call it. PHILOSOPHY APPLIED — Karpathy 'LLM is the scheduler, don't compete with it': the antifragile fix is BETTER CONTEXT, not server-side keyword matchers. As Claude gets better, richer descriptions help more, not less. This contrasts with the originally-planned discover_tools server-side matcher (cut from Phase C — would compete with LLM judgment and DEGRADE as models improve). Thin harnesses + fat skills: harness = the 10 tool definitions, unchanged; skill = the prose descriptions + welcome.js capability map, dramatically expanded. WHAT SHIPS: (1) WELCOME.JS CAPABILITY MAP. New top-level table mapping operator-language ('add a chatbot to my website', 'reply to inbound SMS automatically', 'add hero/services/faq section') to the right SF primitive (AGENT, CONVERSATION, BLOCK) with the entry-point tool. CRITICAL ANTI-PATTERN section explicitly tells the LLM: chat widgets are NOT blocks — don't list_blocks looking for chat. WRONG path → RIGHT path side-by-side. Plus a 'canonical short flow — build a website chatbot' walkthrough showing the 5-call sequence (configure_llm_provider → create_agent → publish_agent test → operator sandbox-tests → publish_agent live). Plus the observability tool list with one-line summaries of when to call each. (2) FIRST_CALL_BANNER reorganized as a capability map first (a/b/c/d/e/f primitives with what-they-are summaries), chatbot canonical flow second, workspace flow third (legacy reference). The banner is what Claude Code shows on first use — anchoring it on capability decisions BEFORE workspace creation re-orients the entire mental model. (3) USE-WHEN TRIGGER PHRASES on every v1.26.x agent tool description. Each description now LEADS with 'USE WHEN USER SAYS:' followed by 4-6 example natural-language phrases, then the technical doc. This makes semantic-match win during tool selection. configure_llm_provider, create_agent, list_agents, publish_agent, update_agent_blueprint, run_agent_evals, tail_agent_conversations, get_agent_conversation, replay_conversation, get_agent_metrics — all 10 updated. create_agent additionally calls out the DON'T-CONFUSE-WITH cases (list_blocks, send_conversation_turn) inline. (4) VERSION constant bumped 1.18.1 → 1.27.1 (was stale 9 versions back). NO new tools, NO new endpoints, NO new migrations, NO new env vars. Backend redeploy NOT required (this only touches the MCP package). MCP republish only. EXPECTED IMPACT: the same 'create a chatbot for Cypress Pine HVAC' prompt that wasted 7 tool calls in v1.27.0 should now resolve in 3-5 calls (configure_llm_provider [if needed] → create_agent → publish_agent test). Phase B (v1.28.0 — build_website_chatbot skill bundle collapsing the 4-call sequence into 1, plus optional auto-inject onto a landing page block) and Phase C (v1.29.0 — get_skill_guide markdown how-tos, voice/email skill bundles, optional tool-name prefix renames) follow.",
73
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.27.0: AGENTS GET A PROPER HOME — discoverable nav + full in-dashboard agent management. PROBLEM v1.27 SOLVES: after v1.26.2 the chat-AI agents lived at /admin/agents (not in the sidebar nav, type-the-URL discovery), confusable with the existing /agents/runs (workflow run log) and /automations (workflow archetype configurator). SF clients had to use Claude Code MCP for ALL agent edits — no in-dashboard editing path. WHAT SHIPS: (1) NAV REWORK. /admin/agents promoted to /agents (operator-facing top-level route, not SF-internal). 'Agents' added to sidebar nav between Bookings and Pages with Bot icon (lucide-react). The existing /agents/runs (workflow runs log, 15 cross-refs) coexists at the same level — Next.js resolves the static 'runs' segment before falling through to dynamic '[id]', so /agents (chat AI list), /agents/<uuid>/* (chat AI detail), and /agents/runs (workflow runs) all route correctly. (2) AGENT DETAIL SHELL. New /agents/[id]/layout.tsx renders the agent header (← All agents back link, name, status pill, version, archetype, slug) + AgentTabs nav with 5 tabs: Overview / Sandbox / Conversations / Settings / Evals. Each child route is a tab body. (3) OVERVIEW TAB (/agents/[id]/page.tsx). At-a-glance stat grid (24h conversations, validator pass rate, eval pass rate, avg latency); daily-token-usage progress bar (green/amber/rose at 70/90%); 3 most recent conversations with quality marks visible; embed snippet copy block. OverviewActions client component lets operator toggle status (draft/test/live/paused) — 'live' triggers the eval gate server-side; failure surfaces the failing scenarios in-line with hint to fix in Settings. (4) SETTINGS TAB (/agents/[id]/settings + settings-client.tsx). INLINE BLUEPRINT EDITOR replicating MCP update_agent_blueprint without leaving the dashboard. Greeting textarea, capability checklist (5 capabilities: look_up_availability, book_appointment, find_my_existing_appointment, escalate_to_human, provide_faq_answer), FAQ rows (add/edit/delete), pricing facts rows (add/edit/delete with label+amount+currency), optional change-note input, Save button. saveAgentBlueprintAction validates with Zod, calls updateAgentBlueprint server action (bumps current_version + writes new agent_versions row), revalidates path, returns new version number. (5) EVALS TAB (/agents/[id]/evals + evals-client.tsx). Lists all 8 platform-owned scenarios (description + severity pill + category) with latest result inline (passed / failed / not run); failed rows show failure reasons + collapsible 'Agent response' detail. 'Run evals now' button calls runEvalsAction → runEvalSuite → returns updated summary. Header shows aggregate pass count + meets-gate badge + last-run timestamp. (6) CONVERSATIONS TAB (existing, polished). Same expand-in-place transcript as v1.26.2 plus QualityMarker client component on every expanded conversation: good/bad pill toggles + notes input. markConversationQualityAction writes operator_quality + operator_notes (already in schema since v1.26.0); collapsed-row preview shows the quality mark + first 60 chars of notes so operators can scan the list for already-reviewed chats. (7) SERVER ACTIONS (lib/agents/actions.ts). saveAgentBlueprintAction, setAgentStatusAction, runEvalsAction, markConversationQualityAction. All workspace-scoped via getOrgId(). Zod-validated input. Each calls revalidatePath so the dashboard reflects new state without hard refresh. (8) DUPLICATE-HEADER TRIM. /agents/[id]/test/page.tsx and /agents/[id]/conversations/page.tsx had their own h1 + back-link UI (from v1.26.2 when they were at /admin/agents); v1.27.0 removes those since the layout.tsx provides them at the parent level. Cleaner, no duplication. NO new migrations (v1.26.0 0044 still covers everything; operator_quality + operator_notes columns already there). NO new env vars. Backend redeploy + npm publish required. DEFERRED to v1.28: real Anthropic-streaming-passthrough on /turn (mid-turn tool_use streaming); voice-receptionist + sms-followup-bot specialized eval scenarios; per-scenario eval history graph; conversation-detail standalone page; 'fold operator-marked good/bad chats into future eval scenarios' (signal pipeline). The existing /agents/runs workflow run log will move to /automations/runs in v1.28 once we've audited the 15 cross-refs.",
74
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.26.2: AGENTS GO SAFE — eval-gated publish + SSE streaming + admin debug surfaces + 5 new debug MCP tools. WHAT SHIPS: (1) EVAL SUITE. New lib/agents/eval-scenarios.ts ships 8 platform-owned scenarios per archetype (website-chatbot first; voice + sms inherit until v1.27/v1.28 specialization): 5 critical (3 prompt-injection + PII safety probes + 2 pricing-discipline checks — refuses-invented-price, refuses-competitor-match) and 3 warning (off-topic refusal, greeting-within-cap, escalation-on-complex-legal). Each scenario = id + description + userMessages[] + expected{responseContains|responseLacks|toolCallsRequired|validatorsAllPassed}. New lib/agents/eval-runner.ts runs each scenario as an ephemeral test conversation (channelMeta.eval_run=true so they don't pollute tail_conversations), captures actuals, persists to agent_evals, returns aggregate {totalRun, passed, failed, passRate, meetsPublishGate, results[]}. (2) EVAL-GATED PUBLISH. lib/agents/store.publishAgent now runs the eval suite before flipping status='live' and rejects with error='eval_gate_failed' when passRate < 87.5% (≥7/8 of 8 default scenarios). Other transitions (draft, test, paused) unrestricted. SF-controlled emergency override via {force:true} (logged). evalSummary returned alongside ok=true so the operator sees what passed. (3) SSE STREAMING ON /turn. POST /api/v1/public/agent/<slug>/turn now branches on Accept: text/event-stream | body.stream=true | ?stream=1: emits 'start' (conversation_id), 'delta' chunks (~28-char word-aware splits, ~22ms gap for typewriter UX), 'done' (conversation_id + validators_critical_failed). Runtime still buffers full response for validator gating BEFORE any byte streams (critical-fail still replaces the whole text). Real Anthropic-passthrough streaming with mid-turn tool-use events queued for v1.27 alongside multi-step tool-call streaming. embed.js (v1.26.2) consumes SSE: assistant message DOM node grows in place as deltas arrive, falls back to JSON if Content-Type isn't text/event-stream. (4) ADMIN DEBUG SURFACES. /(dashboard)/admin/agents — workspace agent roster with status pills, daily-token-usage bars, quick links to /test + /conversations. /(dashboard)/admin/agents/[id]/test — interactive sandbox client (TestSandboxClient) consuming SSE, identical UX to embed widget; surfaces conversation_id so operator can call get_agent_conversation MCP tool to inspect. /(dashboard)/admin/agents/[id]/conversations — server-rendered list of recent customer chats (eval-runs filtered by default), expand-in-place to show full transcript with tool_calls + tool_results + validator pass/fail per assistant turn. (5) 5 NEW DEBUG MCP TOOLS. run_agent_evals({agent_id}) — manual eval suite trigger (publish auto-runs too). tail_agent_conversations({agent_id, limit?, include_eval_runs?}) — newest-first conversation list with first_user_message preview. get_agent_conversation({conversation_id}) — full transcript with all tool_calls + tool_results + validator results per turn. replay_conversation({conversation_id}) — re-run user messages against current blueprint, returns original_turns + replay_turns side-by-side for regression-testing blueprint changes. get_agent_metrics({agent_id, since_hours?}) — aggregate stats over a time window: conversations + turns + tokens_in/out + avg_latency_ms + validator_pass_rate (% of assistant turns where ALL validators passed) + eval_pass_rate (latest result per scenario). NO new migrations (0044 from v1.26.0 still covers all five agent tables). NO new env vars. Backend redeploy + npm publish required. DEFERRED TO v1.27: real Anthropic-streaming-passthrough on /turn (mid-turn tool_use streaming); voice-receptionist + sms-followup-bot specialized eval scenarios; operator-quality marking (good/bad/notes) on /conversations; conversation-detail standalone page (/conversations/[convId]/page.tsx); per-scenario eval history graph.",
75
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.26.1: AGENTS GO LIVE — BYOK + embed.js + 5 new MCP tools. WHAT SHIPS: (1) BYOK (BRING-YOUR-OWN-KEY). SF clients (the agencies + their HVAC/dental/coaching operators) use Claude Code or Cursor to build agents — they already have Anthropic API keys. SF doesn't markup LLM cost; SF charges per agent turn (separate revenue line). lib/agents/runtime.ts swaps from process.env.ANTHROPIC_API_KEY to getAIClient({ orgId }) — the same BYOK helper that lib/ai/client.ts already exposes for the rest of the platform (encrypted at rest in organizations.integrations.anthropic.apiKey with v1. prefix; decryptIfNeeded resolves at call time). When a workspace hasn't configured a key, executeTurn returns { ok: false, reason: 'llm_not_configured', fallbackMessage } — the public turn endpoint surfaces a graceful 'I'm not set up to chat yet — please reach out directly' instead of crashing. (2) EMBED.JS — single-line install on operator's website. New endpoint GET /api/v1/public/agent/<orgSlug>--<agentSlug>/embed.js returns a self-contained IIFE (~3KB) that injects a bottom-right chat bubble + chat panel. Click bubble → panel opens → user types → POST to /turn → response appears. Anonymous session id stored in localStorage (sf_agent_session_<agentSlug>) so multi-message sessions thread together server-side via agent_conversations.anonymous_session_id. Themed via agent's blueprint.greeting + workspace's theme.primaryColor. Returns no-op script (still 200) when agent not 'live' so operator's site console stays clean during draft → test → live progression. CSS namespaced .sf-agent-* (zero collision risk on operator's site). Operator's install snippet: <script src=\"https://app.seldonframe.com/api/v1/public/agent/cypress-pine-hvac--default/embed.js\" async></script>. (3) 5 NEW MCP TOOLS so SF clients build + ship agents from Claude Code without touching the dashboard: configure_llm_provider({ provider: 'anthropic'|'openai', api_key }) — first-run setup, encrypts + writes to organizations.integrations[provider].apiKey. create_agent({ name, archetype, channel, capabilities?, faq?, pricing_facts?, greeting? }) — wraps lib/agents/store.createAgent, returns { agent, embed_url, turn_url, next_steps[] } so the SF client immediately gets the embed snippet to drop on their operator's site. update_agent_blueprint({ agent_id, patch, publish_notes? }) — wraps updateAgentBlueprint, bumps current_version + writes new agent_versions row. publish_agent({ agent_id, status: draft|test|live|paused }) — flips status. list_agents() — workspace-scoped roster with id/name/slug/channel/archetype/status/version/tokens/budget/created. (4) /api/v1/agents route extended with op='set_llm_key' handler so the new MCP tool routes through one endpoint. Validates provider ∈ {anthropic, openai}, api_key length ≥ 10, encrypts via encryptValue (returns 503 encryption_unavailable when ENCRYPTION_KEY env not set), merges into organizations.integrations preserving other providers' entries (kit, mailchimp, etc.). NO new migrations (0044 from v1.26.0 covers all agent tables). NO new env vars beyond ENCRYPTION_KEY (already required for the existing BYOK path). Backend redeploy + npm publish required. DEFERRED TO v1.26.2: SSE streaming on /turn; eval suite runner (executes scenarios from blocks/agent-website-chatbot/SKILL.md against the live agent); eval-gated publish (require ≥7/8 scenarios pass before flipping to live); /admin/agents/[id]/{conversations,test} debug surfaces; MCP debug tools (tail_agent_conversations, get_agent_conversation, replay_conversation, get_agent_metrics).",
76
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.26.0: AGENT FOUNDATION — primitives for building chat / voice / SMS / email agents on top of Soul + Brain. Web chat archetype ships first; voice + SMS queued for v1.27/v1.28. Migration 0044 adds agents / agent_versions / agent_conversations / agent_turns / agent_evals tables. Schema (src/db/schema/agents.ts) typed with AgentBlueprint, AgentToolCall, AgentToolResult, AgentValidatorResult. System prompt composer (lib/agents/prompt.ts) derives prompts from Soul + blueprint, NEVER operator-authored — operators contribute knowledge (FAQ, services, pricing); platform owns the prompt. Five output validators (lib/agents/validators.ts) run on every assistant response: quotes_only_from_soul_pricing (critical — blocks hallucinated $X), no_prompt_injection_echo (critical — blocks 'ignore previous instructions' echoes), no_pii_leak (critical — blocks emails/phones not in user's message), no_avoid_words (warning — Soul voice rule), response_length_under_cap (warning — 600 chars). Critical fail = response replaced with 'let me check + escalate'. Five typed tools (lib/agents/tools.ts) with Zod input schemas: look_up_availability, book_appointment (wraps submitPublicBookingAction — same atomic primitive as /book), find_my_existing_appointment, escalate_to_human (writes portal_messages + activities), provide_faq_answer. Runtime (lib/agents/runtime.ts) executeTurn — loads conv + agent + soul, checks daily token budget, persists user turn, calls Anthropic with tools, loops on tool_use (max 6 iterations), runs validators, persists assistant turn with cost/latency, updates aggregates, activity-bridges first turn to operator's CRM. Non-streaming for v1.26.0; v1.26.1 adds SSE. Public turn endpoint POST /api/v1/public/agent/<orgSlug>--<agentSlug>/turn (anonymous; live/test status required). Agent CRUD endpoint POST /api/v1/agents (op: create | update_blueprint | publish | list — workspace bearer auth). createAgent generates slug, creates v1 in agent_versions, soul-derives blueprint defaults. blocks/agent-website-chatbot/SKILL.md documents the archetype + 8 eval scenarios including prompt-injection + PII-leak adversarial probes. NO new MCP tools yet (HTTP endpoint exposed; MCP-tool wiring + SSE streaming + embed.js + operator debug surfaces land in v1.26.1). NO new env vars beyond ANTHROPIC_API_KEY (already required). Backend redeploy required.",
77
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.25.4: operator 'Today' snapshot widget + remaining polish. NEW: replaces the v1.25.3 gap (where SF agency operators saw 'Newly installed blocks') with an operator-actionable 'Today' overview at the top of /dashboard. Four cards: today's bookings (count + first 3 with time/name), unread customer-portal messages, deals stuck >5 days (closedAt IS NULL + updatedAt < 5d ago), bookings-this-week trend. Each card click-throughs to its source surface (/bookings, /contacts, /deals). Below the cards: 'Up next' list of today's first 3 bookings rendered as time/name/title rows. All queries scoped by session orgId; widget renders only for isOperatorSession. POLISH: SeldonChat (the SF AI assistant floating button) hidden for operator sessions — that's an SF-builder tool. Command palette items split by session type — operator sessions only see Dashboard/Contacts/Deals/Bookings + their contact + deal hits; non-operator sessions get the full SF palette including Soul Marketplace, Studio, Seldon It, Pages, Email, Settings, Recent Activity. NO migrations, NO new env vars. Backend redeploy required.",
78
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.25.3: operator dashboard polish — hide SF-meta / SF-developer surfaces for operator sessions. From first principles: an HVAC owner / dentist doesn't have a build-time, doesn't speak block / soul / BLOCK.md, doesn't manage multiple workspaces, doesn't get support from SF Discord (their agency Acme AI is their support). Hides from /dashboard for operator sessions: (1) 'For the best experience, use Seldon directly from Claude Code with our MCP + Skill' hint banner — SF developer-facing. (2) 'Create New Client OS' button in the welcome header — agency-only action. (3) 'Newly installed blocks' section entirely — build-time SF-meta surface with View Public Page / Open Admin buttons. (4) 'BLOCK.md view metadata' subtitle on Pipeline section — replaced with 'Drag deals between stages to update their status' for operator sessions. (5) 'Docs' link in topbar — points at SF developer docs. (6) HelpButton floating popover — Discord / SF docs / report-a-bug links. (7) 'Discord' / 'Help' link from sidebar (already trimmed in v1.25.1; v1.25.3 fully removes it from operator nav). The welcome heading copy switches from 'This is your calm workspace overview' to 'Here's what's happening at your workspace today' for operator sessions — grounding the page in their business rather than SF-meta workspace concept. Each gate threaded via isOperatorSession prop from /(dashboard)/layout.tsx → DashboardTopbar / Sidebar / dashboard/page.tsx. NO migrations, NO new env vars. Backend redeploy required. v1.25.4 candidate (deferred): replace the now-empty top section with an operator-actionable 'Today snapshot' widget — count of today's bookings, deals needing attention, unread messages.",
79
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.25.2: TWO HOTFIXES from the v1.25.1 production smoke. (1) Operator session now PRECEDES NextAuth in helpers.ts. Pre-1.25.2 getCurrentUser/getOrgId/requireAuth checked NextAuth first, then operator-portal as a fallback. Smoke surfaced 2026-05-07: a user with BOTH a stale NextAuth cookie (from a prior /signup attempt that hit the Verification error) AND a fresh operator-portal cookie was treated as a NextAuth user — full SF admin nav showed instead of the trimmed operator nav. v1.25.2 reorders: operator portal cookie wins when present + valid. Falls through to NextAuth → admin-token otherwise. Intent-based — clicking an operator magic link clearly means the user wants operator portal access. Same precedence applied across getCurrentUser, getOrgId, requireAuth. (2) Dashboard page org_members + organizations queries now gate on isOperatorPortalUserId. Pre-1.25.2 the dashboard page had its own org_members lookup `where userId = synthetic-id` that bypassed the v1.25.0 helpers' opCtx check, crashing with PostgreSQL 22P02 'invalid input syntax for type uuid' (the synthetic id `__sf_operator_portal__:<orgId>` isn't a valid uuid). v1.25.2 short-circuits: when isOperatorPortalUserId(user.id), membershipRows = [{ orgId }] (the operator's single workspace) instead of querying org_members, and directWorkspaceRows queries by organizations.id only (skipping the ownerId/parentUserId clauses that also crash). Defense-in-depth: lib/billing/orgs.ts's listManagedOrganizations + listManagedOrganizationsForUser now early-return [] when user.id doesn't match the UUID_SHAPE regex (8-4-4-4-12 hex). NO migrations, NO new env vars. Backend redeploy required.",
80
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.25.1: hotfix — operator-portal sessions on /dashboard see a TRIMMED nav (Dashboard / Contacts / Deals / Bookings + Help) instead of the full SF nav (Soul Marketplace, Pages, Email, Forms, Automations, Studio, Settings). Pre-1.25.1, v1.25.0 unlocked the admin dashboard for operator sessions but didn't gate the sidebar — operators could see + click into SF-internal pages they shouldn't. v1.25.1: Sidebar component takes new props isOperatorSession + agencyBrandName threaded from the (dashboard) layout. When isOperatorSession=true: nav groups become OVERVIEW (Dashboard) / CRM (Contacts/Deals/Bookings) / ACCOUNT (Help) — Soul Marketplace, Pages, Email, Forms, Automations, Studio, Settings hidden. When agencyBrandName is set (via getEffectiveBrandingForWorkspace), the sidebar's brand subtitle shows the agency name + 'Cypress & Pine HVAC's CRM' subtitle instead of the default 'SeldonFrame / Operating system for modern teams'. Defense-in-depth: hidden nav items are still ROUTABLE if the operator types the URL directly — v1.25.2 will add a server-side gate that 404s SF-internal routes for operator sessions. NO migrations, NO new env vars. Backend redeploy required.",
81
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.25.0: ARCHITECTURAL PIVOT — operator session unlocks the admin dashboard (one source of truth at the route level). Pre-1.25.0 the operator portal at /portal/<slug>/* was a parallel route tree with its own bespoke chrome (light-mode shell, sidebar, agency branding wrapper) that visually drifted from the admin dashboard (different fonts, sizes, spacing, dark vs light). v1.24.0 tried to fix this by sharing components but the bespoke shell was still a separate surface. v1.25.0 takes the cleaner approach: the operator session unlocks the SAME admin dashboard the SF agency operator uses. /dashboard, /contacts, /deals, /bookings — same routes, same nav, same fonts, same theme toggle (light/dark), same UX. Just (a) workspace-scoped to the operator's org via the sf_operator_session cookie and (b) the user can only access ONE workspace (no switcher). HOW IT WORKS: (1) New lib/auth/operator-portal-context.ts adds operator-portal session as a third auth source alongside NextAuth + admin-token. resolveOperatorPortalContext reads sf_operator_session cookie, verifies the JWT (kind=session), produces a synthetic Session-shaped user with `id` carrying the __sf_operator_portal__: prefix (recognizable via isOperatorPortalUserId so users-table writes can no-op, mirroring admin-token treatment), the workspace orgId, and NextAuth-shape fields (planId=null, subscriptionStatus='trialing', etc.) for type compatibility. (2) lib/auth/helpers.ts getCurrentUser / getOrgId / requireAuth / getCurrentWorkspaceRole all gain operator-portal as a third option. Resolution order: NextAuth → operator portal → admin-token. Operator-portal precedes admin-token because magic-link is the dominant white-label entry path. (3) /(dashboard)/layout.tsx detects operator session via isOperatorPortalUserId and skips users-table lookups (same skipUserLookup gate that admin-token already used) — synthesizes plan/billing defaults, hides the workspace switcher (no listManagedOrganizations call). (4) Operator magic-link verifier (/portal/<slug>/magic) and agency-support-session verifier (/portal/<slug>/support-magic) redirect targets switched from /portal/<slug> to /dashboard. Operator clicks email link → cookie set → land at /dashboard which renders the admin shell with their workspace's data. (5) The /portal/<slug>/(operator)/* route tree is collapsed: layout.tsx becomes a session-gate pass-through, page/contacts/deals/bookings/page.tsx all redirect to /dashboard /contacts /deals /bookings respectively. Existing magic-link emails in customer inboxes still work because they hit /portal/<slug>/magic (which now redirects to /dashboard). (6) Bespoke operator-portal-shell / operator-portal-nav / operator-portal-sidebar-nav components are no longer rendered (kept in repo as v1.20-v1.24 history; safe to delete in v1.25.1). v1.24.0's shared page-view components (contacts-list-page-view, deals-list-page-view, bookings-list-page-view) STAY — they're still the data-loading layer behind the admin pages. The hrefBase + readonly props are now vestigial for those components but harmless. WHAT THE OPERATOR SEES: admin dashboard chrome — search palette, light/dark toggle, sidebar nav (Dashboard / Contacts / Deals / Bookings / Pages / Email / Settings), agency-branded workspace name in header. Same fonts, same spacing, same UX as the SF agency operator's dashboard. NO migrations, NO new env vars, NO new MCP tools. Backend redeploy required. DEFERRED to v1.25.1: agency-branded banner on /dashboard when active partner-agency is set (currently the operator sees the SF default chrome — branding lands on emails + the operator portal login but not the dashboard yet); hide SF-internal nav items (Soul Marketplace, Agency, Settings/billing) for operator-portal sessions; dual-auth refactor for write actions like updateContactFieldAction so operator-portal users can edit (currently writes still target NextAuth getOrgId — which now resolves correctly via the new auth seam, so writes likely work; needs verification).",
82
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.24.0: ONE SOURCE OF TRUTH — operator portal /contacts /deals /bookings now render the EXACT SAME COMPONENTS as the admin dashboard equivalents. Pre-1.24.0 the operator portal mirrors I shipped in v1.22 were simpler bespoke versions (basic Twenty-CRM-style tables) that visually didn't match the admin pages. v1.24.0 refactors so /contacts, /deals, and /bookings each have ONE shared view component used by BOTH admin route AND operator portal route — same data fetching, same UI, same UX. ARCHITECTURAL CHANGES: (1) Data primitives accept orgId override. listContacts(options.orgId), listDeals(orgId), listBookings(orgId), listAppointmentTypes(orgId), getDefaultPipeline(orgId), getSoul(orgId), getLabels(orgId), getPersonality(orgId) — all gain optional orgId parameter. When set, skips the session-based getOrgId() call; when null, falls through to the existing session resolution. This is the seam that lets the same query helpers serve both the admin's NextAuth-resolved orgId AND the operator-portal's sf_operator_session-resolved orgId. (2) Three new shared components: <ContactsListPageView orgId searchParams baseHref contactDetailHrefBase dealDetailHrefBase readonly>, <DealsListPageView orgId searchParams readonly>, <BookingsListPageView orgId readonly>. Each does the entire page load + render. The admin /contacts /deals /bookings pages are now 5-line thin wrappers that resolve session→orgId and pass through. (3) ContactsTableView gains contactDetailHrefBase + dealDetailHrefBase + readonly props. /contacts row clicks route to /contacts/<id> (admin) or /portal/<slug>/contacts/<id> (operator) based on the prop. SidePanel + DealsTab + OverviewTab thread these props down. (4) Operator portal mirror pages are now 5-line wrappers too: resolve operator session → orgId → render the same shared component with `readonly` set + workspace-aware hrefBase props. The v1.22 bespoke operator pages (smaller Twenty-CRM tables) are deleted. READ-ONLY MODE: v1.24.0 ships read-only operator mirrors. Inline edit, drag-drop, status changes, bulk-select are wired to NextAuth-backed server actions (e.g. updateContactFieldAction calls getOrgId() which reads NextAuth) that the operator-portal session can't satisfy. ContactsListPageView passes readonly=true from operator routes; the ContactsPageActions header (CSV import + add new) is hidden in readonly mode. v1.24.1 will refactor the write actions for dual-auth so operator-portal users can edit too. DEFERRED TO v1.24.1: /dashboard mirror (1,359-line page; needs careful extraction to avoid regressions); /contacts/<id> detail mirror (Overview/Activity/Deals/Emails/Bookings/Documents/Notes tabs); dual-auth refactor of write server actions so operator portal becomes write-capable. NO migrations, NO new env vars, NO new MCP tools. Backend redeploy required.",
83
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.23.0: month-calendar reschedule + operator dashboard cleanup. WHAT SHIPS: (1) RESCHEDULE NOW USES THE SAME CALENDAR AS /book. v1.22 shipped a date-tabs + slot-grid layout that worked but visually didn't match the /book/<slug> calendar (3-step indicator, full month-grid). v1.23 swaps in CustomerRescheduleCalendar (React) — 3-step indicator (Pick a date / Choose a time / Confirm reschedule), MON-SUN month grid with prev/next navigation, today highlight, past dates disabled, click-day → time-slots fetched via new /api/v1/public/booking-slots endpoint, click-slot → confirmation panel → atomic rescheduleBookingAction. Same backend (single source of truth via listPublicBookingSlotsAction); just better-matched UX surface. New thin GET endpoint /api/v1/public/booking-slots wraps the existing slot enumerator so client-side calendars can fetch availability without re-running the full /book renderer. (2) OPERATOR DASHBOARD CLEANUP. Removed the stale 'Coming in v1.21' card from /portal/<orgSlug> dashboard — those mirror pages (/contacts /deals /bookings) shipped in v1.22 so the placeholder was incorrect. The 'last 8' / 'v1.21 — full activity feed' label on Recent activity replaced with a count-based subtitle ('last N'). Customer-reschedule-client (v1.22 date-tabs implementation) deleted. NO migrations, NO new env vars, NO new MCP tools. Backend redeploy required. v1.24 queued: extract admin /contacts /deals /bookings/<id> into shared components, wire operator portal mirrors to render the same components — true one-source-of-truth so /portal/<slug>/contacts looks IDENTICAL to /contacts (just scoped). Currently the operator-portal mirrors are simpler bespoke versions; v1.24 makes them indistinguishable from the admin pages.",
84
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.22.0: WHITE-LABEL CRM PHASE 4 — operator-portal /contacts /deals /bookings mirrors + agency support sessions + TRUE self-service reschedule. WHAT SHIPS: (1) OPERATOR PORTAL MIRRORS — /portal/<orgSlug>/contacts (Twenty-CRM-style table: Name, Email, Phone, Company, Status pill, Score, Added date, scoped to operator session.orgId), /portal/<orgSlug>/deals (pipeline-grouped view with stage totals, contact links, expected close date, deal value formatted with Intl.NumberFormat), /portal/<orgSlug>/bookings (Upcoming + Past sections, status pills, contact links, scoped queries with ne(template) filter). All three pages use the existing OperatorPortalShell so agency-branded chrome + light Twenty-CRM aesthetic carry through. Sidebar nav refactored from layout-level hardcoded active-state to a client-component (operator-portal-sidebar-nav.tsx) that reads usePathname() — placeholders ('v1.21' badges) gone. (2) AGENCY SUPPORT SESSIONS — migration 0043 adds the agency_support_sessions audit table (agency_id + workspace_id + origin_user_id + started_at + ended_at + ip_hash + user_agent + notes; three indexes for hot-path queries). New lib/operator-portal/support-session.ts with createAgencySupportSession (verifies caller owns the agency via partner_agencies.owner_user_id, mints OperatorTokenPayload kind=session with supportOriginUserId set + 2-hour TTL, writes audit row, returns the support-magic URL) + consumeAgencySupportSession (verifies the support token, sets the operator session cookie). New /portal/<slug>/support-magic route consumes tokens. New /agency dashboard page lists the caller's owned agencies + their attached workspaces with 'Open portal' button per workspace (opens new tab). The existing v1.20 yellow 'Agency support session active' banner in the operator portal layout already lights up automatically when supportOriginUserId is set on the session token. (3) TRUE SELF-SERVICE RESCHEDULE — new /customer/<orgSlug>/reschedule/<bookingId> page with date-tabs + slot-button-grid that reuses the EXISTING listPublicBookingSlotsAction (the same generator that powers /book/<slug> — single source of truth for availability + workday hours + buffer + max-bookings-per-day enforcement). Customer picks a real slot, the new rescheduleBookingAction in lib/customer-portal/appointment-actions.ts atomically validates the slot is in the live availability set, computes new endsAt preserving original duration, UPDATEs bookings.startsAt + endsAt, writes activities row type='booking_rescheduled' with old/new ISO in metadata, emits portal.booking_rescheduled event. After the update every operator surface (contact activity timeline, /portal/<slug>/bookings, /deals pipeline) reflects the new time without any extra wiring — bookings.startsAt is the single field everyone reads from. Customer-portal Reschedule button changed from inline date+time picker to a link out to the new slot-picker page (replacing v1.21.1 'request' semantics with atomic v1.22 self-service). Cancel flow keeps the inline reason textarea — that path was already correct. NO new MCP tools. Migration 0043 already applied to prod via Neon MCP. Test count unchanged (existing 46 tests cover the v1.19-1.21 invariants). Backend redeploy required. Deferred to v1.23: agency support session ended_at tracking on logout; operator-side inline edit + status changes on the mirror tables; /portal/<slug>/contacts/<id> contact-detail page with full activity timeline; rate limit + abuse detection on agency support sessions.",
85
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.21.2: HOTFIX-OF-HOTFIX — v1.21.1's activity-bridge insert was silently skipped on workspaces whose owning user has role != 'owner'. Pre-1.21.1 customer messages didn't appear in operator's contact activity feed (the original bug). v1.21.1 added the bridge but the lookup `eq(users.role, 'owner')` was too strict — Cypress & Pine HVAC's owning user (Maxime, created via manual users-row INSERT during the v1.18 unblock) has role='member' or null, so the activity insert silently no-op'd. v1.21.2: drop the role filter; pick the FIRST user in the workspace, matching the existing booking-activity pattern in lib/bookings/api.ts that's been working since v1.0. Customer-side activity rows (portal_message / booking_cancelled / reschedule_requested) now land reliably regardless of role-string variance across workspaces. NO migrations, NO env vars. Backend hotfix only. v1.22 still queued: operator-portal /contacts /deals /bookings table mirrors; agency 'Open <workspace> portal' support sessions; TRUE self-service reschedule (slot picker integrated with /book).",
86
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.21.1: HOTFIX — customer-portal actions now bridge to operator's contact activity feed + reschedule UI upgraded from free-text textarea to structured date+time picker. TWO BUG CLASSES FIXED: (1) ACTIVITY BRIDGE. Pre-1.21.1 when a customer sent a message via /customer/<orgSlug>/messages, it inserted a row into portal_messages but NOT into activities. The contact's /contacts/<id>?tab=activity feed reads from activities, so customer messages were silently invisible to operators. Same gap existed for booking cancellations (cancelBookingAction) and reschedule requests (requestRescheduleAction) shipped in v1.21.0. v1.21.1: every customer-side action now ALSO inserts an activities row anchored to the workspace owner's userId (since activities.userId is NOT NULL and the actor is a contact, not a user). Activity types added: 'portal_message' (customer sent a message), 'booking_cancelled' (customer cancelled), 'reschedule_requested' (customer asked to reschedule). Each row carries metadata.source='customer_portal' so a future ops view can filter customer-side vs operator-side actions. Subject lines are scannable ('Customer cancelled: Booked consultation' vs operator-CRM 'Booking scheduled'). (2) RESCHEDULE PICKER. Pre-1.21.1 the reschedule action exposed a single free-text textarea ('When would you like to reschedule? e.g. next Tuesday afternoon'). v1.21.1 replaces this with a STRUCTURED DATE + TIME picker (HTML date input min'd to today, HTML time input stepped to 15-min) plus an optional notes textarea. The composed reason saved on the activity row carries both human-readable text + machine-parseable ISO ('Preferred: 2026-05-14 at 14:30. Note: afternoon works best (iso=2026-05-14T14:30)'), so v1.22 can plug this directly into the calendar slot validator and auto-create the new booking on operator approval. The cancel flow keeps the simple textarea ('Reason — optional'). NO migrations, NO new env vars, NO new MCP tools — backend hotfix only. Backend redeploy required. v1.22 still queued for: operator-portal /contacts /deals /bookings table mirrors; agency 'Open <workspace> portal' support sessions + agency_support_sessions audit table; TRUE self-service reschedule (slot picker integrated with /book — atomic update of bookings.startsAt with availability check, replacing the v1.21.1 'request' semantics).",
87
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.21.0: CUSTOMER PORTAL GROUND-UP REBUILD — light Twenty-CRM aesthetic, industry-aware copy, end-customer-focused information architecture. Pre-1.21 the customer portal at /customer/<orgSlug> was a dark-mode stat grid (Messages: 0 / Resources: 0 / Viewed Resources: 0) with operator-jargon nav (Pipeline / My Pipeline / Bookings / Resources). The end customer audience — homeowner who called HVAC, patient at the dentist, client of the coach — has zero use for any of that. v1.21 starts from first principles: what does the END customer actually need? (1) Their next appointment — when, where, who's coming, can I reschedule, can I cancel. (2) Quick way to reach the business — tap-to-call + tap-to-email. (3) History of their relationship with the business. (4) Documents shared with them. The portal is rebuilt around those needs. WHAT SHIPS: (1) Industry-aware copy packs (lib/customer-portal/copy-packs.ts) keyed by organizations.soul.industry — HVAC says 'service visit', dental says 'appointment', coaching says 'session', legal says 'consultation', accounting says 'meeting', medspa says 'appointment', agency says 'meeting'. Provider labels calibrated too (technician / doctor / coach / attorney / accountant / specialist / account manager). General fallback pack for unknown industries. 11 contract tests cover the picker shape (case-insensitive lookup, complete-pack invariant, no copy/paste pluralization bugs). (2) CustomerPortalShell (light mode FORCED regardless of workspace theme.mode — Twenty-CRM-quality customer experiences are light, neutral, dense). Sidebar nav on sm+ (Home / Appointments / Documents / Messages / Account); mobile top-tabs row below sm. Agency-branded chrome via deriveEffectiveBranding (Acme AI logo + 'on Acme AI' subtitle when active partner agency exists). Powered-by-SF footer respects agency.hide_powered_by_badge. Inter-style sans, 1px hairline borders (#E5E5E1), neutral grays (#F7F7F5 bg, #111 text). (3) NEW OVERVIEW page — hero NEXT APPOINTMENT card (date, time, notes, View details / Reschedule / Cancel buttons) + QUICK CONTACT card (tap-to-call + tap-to-email, mobile-first since this is overwhelmingly a phone surface) + RECENT ACTIVITY (2-3 most recent past bookings). 'Welcome back, <name>' with industry-aware subtitle. Empty state for new customers with zero appointments. (4) NEW APPOINTMENTS page (replaces /pipeline + /bookings). Upcoming + Past sections. Each upcoming row exposes inline Reschedule + Cancel actions powered by lib/customer-portal/appointment-actions.ts (cancelBookingAction = atomic update bookings.status='cancelled' scoped to session.contact.id; requestRescheduleAction = emits portal.reschedule_requested event for operator follow-up. Both enforce contact-scope so a customer can't touch another customer's bookings even with a guessed booking_id). (5) NEW ACCOUNT page — read-only contact info on file + tap-to-call/email business + signed-in-as block with sign-out. v1.22 will add self-edit. (6) DOCUMENTS page redesigned (renamed from /resources). Files (uploaded) + Links sections, light-mode rows, download/open buttons. (7) MESSAGES page redesigned with light shell. Same PortalMessagesClient component (data plumbing untouched). (8) Drop /pipeline (operator-jargon meaningless to customers) and /bookings (folded into /appointments). PIPELINE TAB REMOVED FROM NAV — customers don't have pipelines. (9) Auth + data plumbing UNCHANGED — same requirePortalSessionForOrg, listPortalBookings, listPortalDocuments, listPortalMessages, clearPortalSessionAction. Only the chrome + IA + copy change. NO MIGRATIONS, NO new env vars. Test count: +11 (copy pack picker invariants). Backend redeploy required. Deferred to v1.22: full /contacts /deals /bookings table mirrors inside operator portal; agency support sessions ('Open Cypress portal'); customer self-edit on Account page; true self-service reschedule (customer picks new slot from workspace availability) — currently the reschedule action emits an operator-followup event.",
88
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.20.1: HOTFIX — operator-initiated customer-portal invites now send a clickable MAGIC LINK instead of a 6-digit code. Pre-1.20.1 (and pre-1.20 entirely) the operator clicked 'Send invite email' on /contacts/<id>, the contact received an email containing JUST 'your code is 712420' with NO clickable URL. Customers had to know to navigate to /customer/<orgSlug>/login (or /portal/<slug>/login pre-rename) themselves — most don't know the URL. Confirmed in v1.20 production smoke 2026-05-06: customer received the access-code email but couldn't find the portal page; couldn't sign in. v1.20.1: sendPortalInviteAction (lib/portal/admin-actions.ts) now mints a /customer/<slug>/magic?token=... URL via the existing createPortalMagicLink primitive (token bound to a specific contact_id, 30-min TTL) and emails it via a new lib/emails/portal-invite-magic-link.ts template. Customer clicks → /customer/<slug>/magic verifies token + sets session cookie → lands signed-in at /customer/<slug>. Zero typing required. The 6-digit code self-service flow is UNCHANGED for customers who navigate directly to /customer/<slug>/login without an invite email — they type their email, get a code, type the code. That path covers customers who lost or deleted the invite email. Same v1.19 silent-no-op observability pattern: every short-circuit (org_not_found / plan_gate_denied / contact_not_found / portal_access_disabled / resend_not_configured / email_send_failed) emits a structured warn so production monitoring can attribute drop-offs. Same partner-agency branding override as the access-code email (footer + sender substitute when active partner agency is verified). NO migrations, NO env vars, NO new MCP tools — backend hotfix only. Backend redeploy required. The MCP package itself is unchanged in v1.20.1; version bump coupled with backend so they redeploy together. v1.21 still queued for /contacts /deals /bookings table mirrors inside operator portal + agency support sessions + customer-portal industry-aware copy.",
89
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.20.0: WHITE-LABEL CRM PHASE 3 — split the 'portal' concept into two distinct surfaces with the right URLs, auth ergonomics, and design language for each audience. WHY THIS MATTERS: pre-1.20 we had ONE surface called /portal/<orgSlug> doing one job (homeowner-facing customer portal, 6-digit code auth, dark cinematic theme). The white-label use case wants a DIFFERENT surface: a branded admin dashboard for sub-tenant operators (the HVAC owner / dentist / accountant who runs a workspace that an SF agency partner has white-labeled to them). One URL serving two audiences with conflicting auth ergonomics + UX languages was structurally wrong. v1.20 splits them. WHAT SHIPS: (1) RENAME /portal/<orgSlug>/* → /customer/<orgSlug>/* — the homeowner customer portal. Behavior unchanged (6-digit code auth, customer-facing copy, magic-code email). All 32 referencing files updated: lib/portal/auth.ts, lib/page-blocks/portal/structure.ts (customer_portal_url field surfaced via MCP), customer-login.tsx, portal-access-card.tsx, seldon/block-installer.ts, hub, settings/client-portal, api/v1/portal/invite, etc. Email-template comments + log lines also updated for clarity. (2) NEW OPERATOR PORTAL at /portal/<orgSlug>/* — the sub-tenant operator's branded admin dashboard. Twenty-CRM-inspired light-mode aesthetic (neutral grays, Inter-style sans, 1px hairline borders, dense rows, sidebar nav). Magic-link auth (single-use, 15-min TTL, JWT-only — no DB row, v1.21 adds nonce enforcement) instead of 6-digit code; operator-grade clickable URL, not consumer-grade type-the-code. Agency-branded chrome via deriveEffectiveBranding (Acme AI logo + 'on Acme AI' subtitle in header when active partner-agency exists; 'Powered by SeldonFrame' footer hidden when agency.hide_powered_by_badge=true). Sidebar nav exposes Dashboard (LIVE in v1.20) + Contacts/Deals/Bookings (placeholders for v1.21 mirrors). Dashboard surface: 4 stat cards (contacts, active deals, upcoming bookings, recent activity) + recent-activity feed, all scoped to session.orgId so URL slug tampering can't read another workspace's data. (3) OPERATOR INVITE UI on /settings/team. New OperatorInviteCard component lets the workspace owner mint a magic-link sign-in for any email — recipient becomes an operator of this workspace via the branded /portal/<orgSlug>. Email template (lib/emails/operator-magic-link.ts) carries optional 'invitedByName' for trust + context, agency-branded footer when applicable, distinct subject line ('Sign in to Cypress & Pine HVAC') from the customer-portal email. (4) AGENCY-IMPERSONATION HOOK: OperatorTokenPayload carries optional supportOriginUserId. When a session token has it set, the operator portal layout renders a yellow banner ('Agency support session active — you are signed in as <email>. All actions are audit-logged'). v1.21 will wire up the agency-side trigger (Acme AI clicks 'Open Cypress portal' from their agency dashboard → mints support-session token) + audit-log table. (5) SECURITY MODEL: Magic-link token is JWT-style HMAC-signed (kind='magic', 15-min TTL). The /portal/<slug>/magic route consumes it atomically, swaps it for a session cookie (kind='session', 7-day TTL, httpOnly, sameSite=lax, secure-in-prod). The session cookie is a DIFFERENT token shape so the magic-link can't be used as a session cookie even if leaked — the kind discriminator gates this. 10 contract tests cover the token primitives: round-trips, expiry, signature tampering, malformed input, kind discriminator, supportOriginUserId carry-through. (6) /contacts/<id>'s 'Send invite email' UNCHANGED in v1.20 (just URL-update from /portal → /customer). Operator invites originate from /settings/team (this v1.20 add); customer invites still originate from /contacts/<id> with the existing 6-digit code flow. Two clear surfaces, two clear flows, no ambiguity. v1.21 candidates: full /contacts /deals /bookings table mirrors inside /portal/<slug>; agency-side 'Open <workspace> portal' support-session UI + agency_support_sessions audit table; customer portal industry-aware copy (HVAC says 'service visits'; dental says 'appointments'); UX tightening on /customer/<slug> overview page. v1.22: agency self-serve mgmt + custom domains. NO MIGRATIONS. NO new env vars. Backend redeploy required. The MCP package itself is unchanged in v1.20; version bump coupled with backend so they redeploy together.",
90
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.19.0: STABILITY BUNDLE — five focused root-cause fixes after the v1.18 white-label CRM smoke. (1) PORTAL ACCESS-CODE CASE-INSENSITIVE LOOKUP + SILENT-NO-OP OBSERVABILITY. requestPortalAccessCodeAction + verifyPortalAccessCodeAction now normalize email at action entry (trim + lowercase) AND use lower(stored_email) at the SQL compare, so a customer typing dresslikeAG@Gmail.com still matches a stored dresslikeag@gmail.com. Pre-1.19 the silent-success branches dropped requests with zero log signal — now each branch (org_not_found / plan_gate_denied / contact_not_found / portal_access_disabled) emits a structured warn so production monitoring can attribute every silent-no-op to a specific cause. (2) POLYMORPHIC AGENCY OWNERSHIP. Migration 0041 adds owner_workspace_id to partner_agencies + at-least-one-owner CHECK + lookup index. registerPartnerAgency / attachWorkspaceToAgency / detachWorkspaceFromAgency / registerAgencySenderDomain / verifyAgencySenderDomain now accept EITHER ownerUserId OR ownerWorkspaceId; the API route /api/v1/partner-agencies resolves both identities from the bearer's workspace and falls back to ownerWorkspaceId = guard.orgId when organizations.owner_id is null. Anonymous workspaces (create_workspace_v2 + the v1.7.3 NextAuth-bug claimed-but-unlinked path) can now register agencies natively without a manual users-row INSERT unblock. 12 new contract tests covering the predicate (user-only, workspace-only, dual-pointer, empty caller, null-vs-undefined ambiguity, anonymous-workspace-as-actor). (3) DUPLICATE-CONTACT UNIQUENESS. Migration 0042 adds a partial unique index on contacts (org_id, lower(email)) WHERE email IS NOT NULL — combined with v1.19's case-insensitive lookup, this structurally prevents the dup-row class that produced the v1.18 'no email arrived' bug (two contact rows with the same lowercased email, lookup .limit(1) returned the wrong one with portal_access_enabled=false). Defensive backfill repoints FKs from loser → winner across all 14 contact-referencing tables (activities/bookings/conversations/deals/emails/intake_forms/invoices/payments/portal_access_codes/portal_documents/portal_messages/portal_resources/sms_messages/subscriptions/workspace_records) before deleting losers; idempotent — safe to re-run. Prod confirmed 0 dups before queuing the migration. (4) NEXTAUTH USERS-ROW ROOT-CAUSE FIX — SELF-HEALING JWT RECOVERY. The session strategy is JWT, so token.sub can drift from the actual users.id row in three ways (createUser failed mid-flow, JWT minted from a deleted-then-recreated user, cross-deployment carry-over). Pre-1.19 the v1.7.3 synthesizer prevented the uncaught-throw crash but left the user permanently in a 'signed in but locked in synthesized empty record' dead-state. v1.19 the JWT callback now: on byId miss, falls back to lookup by lower(email); on email match, re-anchors token.sub to that user's id silently; on neither match, logs a structured orphan_token_no_email_match warn for production monitoring to count. 8 new tests covering normalization shape + the 3-state classifier (resolved / self_healed / orphan). (5) PORTAL EXPERIENCE POLISH. The /portal/<orgSlug> client layout + overview page were using legacy crm-card / crm-button-secondary / crm-button-ghost utility classes that didn't pick up workspace branding (every workspace's portal looked SeldonFrame-blue regardless of theme). v1.19 wraps the inner client area in PortalLayout (PublicThemeProvider + branded header) and re-themes nav links, welcome card, stat cards, and message rows with --sf-* CSS variables (--sf-card-bg / --sf-text / --sf-muted / --sf-border / --sf-radius / --sf-primary / --sf-bg). Auth + data plumbing UNCHANGED — same requirePortalSessionForOrg / listPortalMessages / clearPortalSessionAction calls; only the chrome is re-themed. Portal now visually descends from the branded login experience end-to-end. NO new MCP tools; this is a backend stability ship. The MCP package itself is unchanged; version bump is coupled with the backend so they redeploy together. ⚠️ BACKEND REDEPLOY REQUIRED. Migrations 0041 + 0042 already applied to prod via Neon MCP. v1.20 — agency self-serve management tools + nightly plan-gate sweep (deferred, fresh session). v1.21 — agency custom domain + per-client wildcard subdomains via Vercel (deferred, fresh session).",
91
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.18.1: HOTFIX — register_partner_agency_sender_domain is now idempotent. Smoke surfaced 2026-05-06: agency tried to register a domain that was already registered in our Resend account (from a prior smoke), got Resend 403 'The <name> domain has been registered already'. Pre-1.18.1 the operator dead-ended with no recovery. v1.18.1: when create returns 403/already-registered, we list all domains in our Resend account, find the existing record by name, fetch its DNS records, and use that domain_id + records as if create had succeeded. If the existing record happens to be already verified by Resend (DNS resolved at some prior point), we mirror that into our agency row's verified_sender_at and skip the DNS-add prompt entirely. Edge case: if Resend says the domain is already registered but it's NOT in our account list, we surface a structured error pointing the operator at a different domain or support — that's the 'registered under a different Resend account' case which is unrecoverable without contacting Resend. NO migrations, NO env vars, NO new deps. The MCP package itself is unchanged in v1.18.1 — version bump coupled with backend so they redeploy together.",
92
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.18.0: WHITE-LABEL CRM PHASE 2 — Resend per-agency sender + agency-branded transactional emails. v1.17 shipped the partner-agency hierarchy. v1.18 closes the loop on email branding: agencies' clients now receive transactional emails (portal-access-code in v1.18.0; welcome + admin-link queued for v1.18.1) FROM the agency's verified domain WITH the agency's brand name in the template, instead of welcome@seldonframe.com / 'on SeldonFrame'. WHAT SHIPS: (1) Resend Domains API integration (lib/integrations/resend-domains.ts) — createResendSenderDomain (POST /domains), getResendSenderDomain (GET /domains/:id), triggerResendDomainVerification (POST /domains/:id/verify), removeResendSenderDomain. Uses the existing RESEND_API_KEY (no new env var). Each agency's sender domain registered UNDER our Resend account (no separate Resend account per agency). (2) Sender-domain orchestration (lib/partner-agencies/sender-domain.ts) — registerAgencySenderDomain creates the Resend domain + persists resend_domain_id + sender_email_address on the agency row (verified_sender_at left NULL until verification confirms); verifyAgencySenderDomain triggers Resend's verify endpoint, persists verified_sender_at on first success. (3) Two new MCP tools: register_partner_agency_sender_domain (returns DNS records the agency must add at their registrar), verify_partner_agency_sender_domain (idempotent — safe to call while DNS propagates). (4) Portal-access-code email wired to agency branding: when workspace has active agency with verified_sender_at, the email goes FROM the agency's domain + 'on <Agency Name>' in the footer + (optionally) the agency's logo in template. Falls back to SF defaults when no agency / not verified — defense-in-depth via deriveEffectiveBranding (only exposes sender_email_address when verified_sender_at is populated). (5) Email template extended: PortalAccessCodeEmailRequest gains brandName / logoUrl / supportUrl optional fields; HTML + text bodies branch on brandName for footer text. SECURITY: agencies only operate on their OWN agency rows (caller_owns_agency check at every mutation). Resend's standard SPF/DKIM/DMARC enforcement covers email-spoofing risk. Test count: 2539 (unchanged from v1.17 — v1.18 is wiring + integration; the security-critical pure helpers were already covered by v1.17's tests). Typecheck clean. Backward compatible. NO migrations (v1.17's 0040 is sufficient). v1.18.1 candidate (this session if time permits, else fresh): wire welcome + device-auth + admin-link emails to also use agency branding + admin dashboard chrome substitution. v1.19 — agency self-serve management tools + nightly plan-gate sweep. v1.20 — agency custom domain + per-client wildcard subdomains via Vercel.",
93
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.17.0: WHITE-LABEL CRM PHASE 1 — partner-agency hierarchy + foundational primitives. SeldonFrame's Scale-tier customers (agencies / solopreneurs reselling AI-native Business OS to SMBs like HVAC contractors / dentists / lawyers / realtors) can now register as a partner agency, attach client workspaces to that agency, and have those workspaces' chrome (logo, colors, sender, support pointers, 'Powered by' badge) substitute the agency's branding for SeldonFrame's. WHAT SHIPS: (1) Migration 0040 — new partner_agencies table (id, name, slug, logo_url, colors, support pointers, sender_email_address + verified_sender_at for v1.18, agency_domain + agency_domain_verified_at for v1.20, owner_user_id, status: pending|active|suspended|archived, hide_powered_by_badge). organizations.parent_agency_id FK column. Applied to prod via Neon MCP. (2) Drizzle schema modules: partner-agencies.ts + organizations.ts extended with parentAgencyId. (3) Pure helper deriveEffectiveBranding(agency, workspaceName) — decides chrome (SF default vs agency-substituted). Defense-in-depth: doesn't expose sender_email_address until verified_sender_at is populated; doesn't expose agency_domain until agency_domain_verified_at is populated. Plan-gate semantics: only 'active' agencies substitute chrome; pending/suspended/archived fall back to SF defaults (data preserved). (4) DB-loading wrapper getEffectiveBrandingForWorkspace(workspaceId) for chrome consumers. (5) State management: registerPartnerAgency (Scale-tier check + slug uniqueness), attachWorkspaceToAgency (caller-owns-both check), detachWorkspaceFromAgency (caller is workspace owner OR agency owner). (6) Three new MCP tools: register_partner_agency, attach_workspace_to_agency, detach_workspace_from_agency. (7) shouldShowPoweredByBadgeForOrg now respects agency.hide_powered_by_badge for active agencies — first chrome surface wired up. (8) 14 new unit tests covering branding-derivation in all status states, color/sender/domain echo, defense-in-depth on unverified sender + domain. Test count: 2539 (was 2525). NO new env vars (Resend + Vercel come into play in v1.18 + v1.20). Plan gate: agency creation requires Scale-tier ownership; non-Scale callers get the agency in 'pending' status (chrome substitution only activates on active). v1.18 (next ship) — wire the dashboard chrome substitution + welcome/admin/portal-access-code emails to use the agency's verified Resend sender. v1.19 — agency self-serve management tools (list_my_agencies, update_partner_agency, list_my_workspaces). v1.20 — agency custom domain (crm.acmeai.com) with per-client wildcard subdomains via Vercel.",
94
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.16.2: HOTFIX — public booking + intake POST handlers correctly resolve workspace on custom domains. The C4 booking client and C5 intake client both fall back to hostname.split('.')[0] when the path-based orgSlug is missing — fine for <slug>.app.seldonframe.com (the segment IS the slug), broken for custom domains (hvac.tirionforge.com → 'hvac' is just the operator's chosen subdomain, not the workspace slug 'cypress-pine-hvac'). Reproduced 2026-05-06: visitor on hvac.tirionforge.com/book got 'Couldn't book that time'; logs showed booking_context_not_found with org_slug='hvac' (no template booking row exists for that — the workspace's slug is 'cypress-pine-hvac'). Fix: new resolveWorkspaceSlugFromRequestWithCustomDomains async helper that tries the existing subdomain extraction first, then falls back to a workspace_domains lookup by full hostname → returns the canonical workspace slug. Both /api/v1/public/bookings and /api/v1/public/intake routes now PREFER the host-derived slug (when a verified custom domain matches) over body's orgSlug, since on custom domains the body value is whatever the client guessed from the URL — unreliable. Slug-source diagnostic now reports custom_domain | body | subdomain_fallback for funnel observability. NO migrations, NO env vars, NO new deps. The MCP package itself didn't change in v1.16.2 — version bump coupled with backend so they redeploy together.",
95
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.16.1: HOTFIX — portal access-code email actually gets sent. Discovered during the v1.16 smoke: customer hit /portal/<orgSlug>/login, entered their email, but no code arrived. Two bugs: (1) lookup is silently no-op when the entered email doesn't match a contact (security feature, not a bug — operator just needs to enter the contact's actual email). (2) THE REAL BUG: requestPortalAccessCodeAction generated the code, hashed it, persisted to portal_access_codes — and then RETURNED. No call to Resend, no email infrastructure wired up. Codes were piling up in the DB unread; pre-v1.16.1 there was no way for customers to actually receive them. v1.16.1 wires it up: new packages/crm/src/lib/emails/portal-access-code.ts mirrors the device-auth email pattern (verified welcome@seldonframe.com sender, Resend HTTPS POST, sandbox-from fallback, structured error logs). requestPortalAccessCodeAction now calls sendPortalAccessCodeEmail after persistence; failures are logged but don't change the success response (still no info-leak via timing/error). getOrgBySlug widened to also return `name` so the email subject reads 'Your Cypress & Pine HVAC sign-in code: 123456' instead of generic. Email body shows the code in a monospace pill, includes 15-minute expiration. NO migrations, NO new env vars (uses existing RESEND_API_KEY + RESEND_FROM_ADDRESS), NO new deps. The MCP package itself didn't change in v1.16.1 — version bumps coupled with backend so they redeploy together.",
96
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.16.0: WIRE PORTAL TEMPLATE INTO CUSTOMER-FACING ROUTE. v1.15 shipped the templates + renderer + per-customer embeds; the operator could compose templates and verify via preview_portal MCP tool, but customers themselves had no URL to actually SEE the rendered template. v1.16 closes the loop: the existing /portal/[orgSlug] customer-facing route (with magic-link auth already in place pre-v1.16) now renders the operator-defined composite template ABOVE the existing stats grid. WHAT SHIPS: (1) renderPortalForCustomer({ orgId, contactId, workspaceTimezone, workspaceContext }) — server-side helper that loads the template, builds CustomerRenderContext via buildCustomerContext, renders each section, returns html + css. Returns null for empty templates so the page falls through to the existing stats-only view (additive change — workspaces without portal templates see the same UX as pre-v1.16). (2) app/portal/[orgSlug]/(client)/page.tsx wires the helper inline. Existing magic-link session auth provides orgId + contactId; renderPortalForCustomer is awaited in parallel with the existing message + resource queries (cheap). (3) CSS strategy: inject COMPOSITE_CSS + a small token-aliases block scoped to .sf-cmp-portal-host that maps the portal page's --color-primary etc. tokens onto the composite primitive's expected --sf-bg / --sf-text / --sf-border / --sf-primary / --sf-accent variable names. No collisions with the surrounding portal layout. (4) UX FIX from v1.15: get_portal_structure / add_portal_section / etc. used to return preview_url pointing at the MCP-only endpoint (browser-clickable look but required workspace bearer auth — confusing). v1.16 renames the field to customer_portal_url and points at the actual live URL customers visit (https://<slug>.app.seldonframe.com/portal/<slug>). For preview during template-design, operators still use the preview_portal MCP tool. Test count: 2525 (unchanged; v1.16 is a wiring change to existing tested code paths). Typecheck clean. Backward compatible — empty templates fall through cleanly. NO migrations, NO env vars, NO new deps. AUTH MODEL: customers reach the portal via magic-link sent from the operator's admin dashboard (existing pre-v1.16 flow); session is contact-scoped via signPortalSession; renderPortalForCustomer trusts the session's contactId. v1.17+ candidate: stable section IDs for cross-call composition without re-reads.",
97
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.15.0: PORTAL COMPOSITE TREES + CUSTOMER.* EMBEDS. The composite primitive vocabulary (12 kinds, v1.12) now extends to the customer-portal surface via 5 new embed.ref values that pull per-customer data: customer.contact_info, customer.next_appointment, customer.recent_appointments, customer.documents, customer.deals. WHY (decisive (α) over (β) on every architectural principle): a portal block IS a landing block with a different render context — same structure, different data binding, different audience. Building a separate portal pipeline (β) would have meant 2× the harness, 2× the agent vocabulary, no compounding of LLM improvements across surfaces. (α) reuses the composite engine; the only new code is per-customer embed render branches + auth-scoped DB resolvers + a workspace-level template store. WHAT SHIPS: (1) Schema extension — 5 new customer.* values added to embed.ref enum; existing 5 workspace embeds still work; unknown refs (customer.password etc) still rejected. (2) CustomerRenderContext extends CompositeRenderContext with customer + 4 per-customer data fields. assembleCustomerContext THROWS on missing customer.id (auth-scope identity, never default to empty). (3) Per-customer DB resolvers (5 functions, each takes orgId+customerId as REQUIRED args, returns auth-scoped data). All five run in parallel via buildCustomerContext. (4) Portal-template storage on organizations.settings.portal_template (CompositeNode[]). One template per workspace; every customer renders against their own data. (5) Five portal MCP tools: get_portal_structure, add_portal_section, update_portal_section, move_portal_section, delete_portal_section. Same atomic-primitive pattern as v1.11/v1.13/v1.14. Empty templates are valid (different from landing's minimum-1 rule — a portal with no template just shows built-in tabs). (6) preview_portal({ workspace_id, contact_id }) renders the template against a real contact for visual verification before customers see it. (7) Renderer extensions: 5 new render branches in renderEmbed (one per customer.* ref). HTML-escaped customer-supplied data (no XSS via document filename or appointment title). (8) Composite SKILL.md addendum: portal patterns (WELCOME / NEXT-VISIT / DOCS+DEALS-SIDE-BY-SIDE), portal voice rules (second-person, customer-addressed), anti-patterns (no cross-customer data, no marketing content). Plus the 'check for near-duplicates before add_composite_section' rule from the v1.14 followup. Test count: 2525 (was 2495; +30: schema/render/assemble + portal-structure pure helpers + XSS + auth-scope contract). NO migrations (template stored on existing settings jsonb), NO env vars, NO new deps. Backward compatible. v1.16 candidate: wire the portal template into a customer-facing rendered route at /portal/<contactId> (currently the rendered HTML is exposed only via the operator-facing preview endpoint).",
98
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.14.0: BOOKING FORM STRUCTURAL PRIMITIVES + COMPOSITE CINEMATIC CSS FIX. Five new tools mirror the v1.13 intake-structure pattern but for the booking form, with one critical addition: STANDARD-FIELD CONTRACT — fullName + email at indices 0/1 are server-owned (the renderer + public POST handler require them per the v1.4.2 fix). Destructive ops on indices 0/1 are rejected by design so an operator cannot accidentally break the booking flow. WHAT SHIPS: (1) get_booking_structure — title, description, duration, fields with is_standard flag + 1-line preview. (2) add_booking_field({ field, position? }) — minimum position 2 (slots 0/1 reserved). Allowed types: text, textarea, email, phone, select. ID uniqueness enforced; cannot be 'fullName' or 'email'. (3) move_booking_field({ from_index, to_index }) — both indices must be >= 2. (4) delete_booking_field({ index }) — index must be >= 2; floor is 'just the 2 standards'. (5) update_booking_field({ index, patch }) — index must be >= 2; ID changes blocked from colliding with another field OR with reserved standard ids. Persistence runs through mergeBookingFormFields as defense-in-depth — even if a primitive somehow let a mutation through, the merge re-prepends the standards before persistence. (6) BUNDLED CSS FIX: composite-block renderer adds .sf-frame.sf-cinematic .sf-cmp-* and .sf-frame.sf-light .sf-cmp-* overrides for headline / heading / subhead / text / card / divider / list-x / stat-label / embed-* — fixes the dark-on-dark headline + card-heading bug observed in the v1.12 smoke (composite section text was using var(--sf-text) which resolved to a dark color in workspaces tuned for light backgrounds, then rendered against the cinematic dark backdrop). Mirrors the override pattern in cinematic-overlay.ts for typed sections. Test count: 2495 (was 2475; +20 booking tests). NO migrations, NO env vars, NO new deps. Backward compatible. v1.15: portal blocks (composite tree on customer portal surface, with customer-data embed refs).",
99
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.13.0: INTAKE FORM STRUCTURAL PRIMITIVES — five new tools mirror the v1.11 landing-structure pattern but for the intake form surface (linear, no nesting). Operators can now ask 'add a phone field', 'put email at the top', 'rename mobile to cellphone', 'remove the rating question' and the agent calls one atomic tool. WHY: pre-v1.13 the agent had to use persist_block(intake) which REPLACES the whole questions array — a bad fit for incremental edits. v1.13 lets the agent do single-field operations without touching the rest of the form. WHAT SHIPS: (1) get_intake_structure({workspace_id}) — returns title, description, indexed list of fields with type + label + required + 1-line preview. (2) add_intake_field({workspace_id, field, position?}) — append (default) or insert. ID uniqueness enforced. (3) move_intake_field({workspace_id, from_index, to_index}) — splice semantics (atomic). (4) delete_intake_field({workspace_id, index}) — refuses to leave 0 fields (public submit needs ≥1). (5) update_intake_field({workspace_id, index, patch}) — patch any subset of {id, type, label, helper, required, options, ratingScale, validation, showIf}; ID changes blocked from colliding. Persistence touches BOTH Blueprint.intake.questions AND intake_forms.fields (the public POST handler reads the latter); both updated atomically per mutation, formbricks-stack-v1 re-renders. (6) 23 new unit tests covering applyAddField (append / insert / id collision / out-of-range), applyMoveField, applyDeleteField (boundary indices / minimum-1 guard), applyUpdateField (patch / id-collision / type+options together / empty-patch reject), and deriveFieldPreview (label + type + required marker + option count + truncation). Test count: 2475 (was 2452). DESIGN: same thin-harness/fat-skill pattern. Server only validates + persists. Agent's LLM picks WHICH field by reading get_intake_structure previews. Antifragile: as LLMs improve at intent-mapping, output quality rises with zero harness changes. NO migrations, NO env vars, NO new deps. Backward compatible — existing persist_block(intake) still works for full-form replaces.",
100
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.12.0: COMPOSITE-TREE PRIMITIVES — operators can now manifest ANY landing-page section by description (comparison columns, pricing tiers, 'how it works' steps, stats rows, side-by-side image+text, custom CTAs) without server-side type-per-block work. The agent's LLM composes a tree from 12 low-level primitives; the server validates + renders. WHY: typed blocks (hero, services, about, faq, cta) require dedicated SKILL.md + Zod schema + renderer mapping per type — fine for high-stakes surfaces, too rigid for the long tail of operator requests. WHAT SHIPS: (1) New SectionComposite section type — holds a recursive CompositeNode tree. (2) 12 primitive node kinds: containers (section / row / col / card), content leaves (heading / text / image / list / button / stat), special (embed / divider / spacer). embed.ref ∈ {services, faq, testimonials, hours, phone} pulls workspace-data into the section without re-typing. (3) Zod schema + structural validators: tree depth ≤ 4, children-per-container caps (section ≤ 8, row ≤ 4, card ≤ 8, list ≤ 12), heading levels descend without skipping, all text fields trim + length-cap. (4) Theme-aware renderer (~3 KB shared CSS, emitted once per page). Mobile-responsive by default (rows stack on narrow viewports). All operator/LLM-supplied text HTML-escaped on emit (no XSS). External nav buttons get target=_blank rel=noopener. (5) Voice scanner — scans all text-bearing fields against soul.voice.avoidWords; returns warnings (not errors) for the agent to self-correct on retry. (6) 2 new MCP tools: add_composite_section({ workspace_id, tree, position? }) and update_composite_section({ workspace_id, index, tree }). (7) get_block_skill('composite') serves a hand-written SKILL.md with the primitive vocabulary, validation rules, voice guidance, and 7 worked patterns (COMPARISON, STATS, HOW-IT-WORKS, SIDE-BY-SIDE, CALL-CTA, FEATURES-GRID, EMBED-DRIVEN-SERVICES). (8) 45 new unit tests (27 validation + 18 render). DESIGN PRINCIPLES: thin harness (server only validates + renders; no LLM judgment), fat skill (ONE skill file with vocabulary + patterns; agent's LLM does composition), antifragile (as LLMs improve at composing trees, validation_warnings rate drops without harness changes), permissive-on-read/strict-on-write (old persisted trees keep rendering even if schema evolves; new writes must validate). Test count: 2452 (was 2407). Backward compatible. NO migrations, NO env vars, NO new deps. Existing typed blocks (hero/services/etc.) untouched. v1.13 candidate: form primitives (intake field add/move/delete/update). v1.14: composite trees on the customer portal surface with customer-data embed refs.",
101
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.11.0: STRUCTURAL PRIMITIVES — three new tools that cut landing-page restructuring time from minutes to seconds, with index-based addressing that handles duplicate section types. WHY: v1.10's reorder_landing_sections used type-based addressing ('hero', 'services-grid', 'faq', ...) which breaks when types repeat (smoke test 2026-05-05: a workspace had two 'services-grid' sections — one grid-3 services, one stats — and the reorder API correctly refused as ambiguous, but the agent had no way to disambiguate without fetching + PowerShell-parsing landing_pages.blueprintJson, which took 8+ minutes). WHAT SHIPS: (1) get_landing_structure({workspace_id}) — returns ordered sections with index (0..N-1) + type + 1-line preview ('Vancouver's Trusted HVAC Family — Same-Day Service' for hero, '3 services (grid-3)' vs 'stats — 4 numbers' for services-grid duplicates). Cheap one-DB-read. Replaces the v1.10 fetch-and-parse workflow. (2) move_section({workspace_id, from_index, to_index}) — atomic single-section move with splice semantics (section at from_index is removed, then inserted at to_index in the result). Handles duplicate types because index is unambiguous. Existing reorder_landing_sections stays as the bulk path for clean cases. (3) delete_section({workspace_id, index}) — atomic single-section remove. Refuses to leave 0 sections (minimum is 1). Use to clean up unintended duplicates from v2 generation artifacts. (4) 22 new unit tests covering applyMove (forward / backward / no-op / immutability / out-of-range), applyDelete (boundary indices / minimum-1-section guard / immutability), and derivePreview (each section type emits a useful 1-liner; services-grid grid-3 vs stats are distinguishable; long text truncates). DESIGN PRINCIPLES: thin harness (server only does mechanical reorder/delete + re-render; no LLM judgment), fat skill (agent's LLM picks WHICH section based on operator intent + preview text), antifragile (as LLMs improve at intent-mapping, output quality rises with zero harness changes). Test count: 2407 (was 2385). Time-to-restructure: 8 min → ~3 sec. NO migrations, NO env vars, NO new deps. Backward compatible. v1.12 candidate: add_section + new section primitives (comparison block, pricing-table, gallery) — separate larger work.",
102
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.10.1: HOTFIX — upload_workspace_image now accepts image_url + local_file_path, eliminating the agent-side base64 round-trip that v1.10.0 forced. WHY: v1.10.0's image_data_b64-only design made the agent base64-encode bytes into the JSON tool argument; the encoded string had to fit in the agent's tool-input token budget (~16 KB base64 ≈ ~12 KB raw). A 100 KB logo forced the agent into manual resize loops (verified 2026-05-05 smoke test: 7m 31s for one Cloudinary URL upload — 4 resize iterations before bytes fit). WHAT SHIPS: (1) image_url body field — SF backend fetches the URL directly via fetch + streamed read with running byte counter (5 MB cap, 5s timeout). SSRF guards: HTTPS-only, loopback / RFC1918 private / link-local IPv4 (incl. 169.254.169.254 cloud metadata) / IPv6 ::1 rejected. file_name + content_type auto-derived from URL path extension; falls back to response Content-Type header for extensionless URLs. (2) local_file_path MCP-client branch — reads the file in the npm-package process (which runs on the operator's machine, no agent token budget involved), base64-encodes for transport to the SF backend. file_name + content_type auto-derived from path extension. (3) image_data_b64 unchanged — kept as a backward-compatible fallback for callers that already have bytes in hand. (4) Tool descriptor explicitly ranks the three sources (image_url > local_file_path > image_data_b64) so the agent picks correctly. (5) 18 new unit tests covering URL validation (HTTPS-only enforcement, loopback / RFC1918 / link-local rejection, public IPv4 acceptance, malformed URL rejection), content-type derivation from URL extension (handles query strings), and file-name basename extraction. Test count: 2385 (was 2367). Time-to-upload-a-logo: ~7 min → ~3 sec (>99% reduction). NO migrations, NO new env vars, NO new dependencies. Backward compatible.",
103
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.10.0: TIER 2 CUSTOMIZE TOOLS — three new operator-driven block-customization tools that respect the thin-harness/fat-skill principle (server only assembles + persists; the IDE agent's LLM does all creative work, so quality compounds as models improve with zero MCP changes). (1) regenerate_block({ workspace_id, block_name, new_instructions? }) — bundles current_props + workspace_summary (business name, industry, services, voice from organizations.soul) + brain_patterns (cross-workspace patterns for this vertical) + customization_history + the operator's new_instructions, returns next_step prose telling the agent: fetch SKILL.md, generate new props, call persist_block with customization: { prompt: <new_instructions> }. Handles first_generation (block never persisted) AND regenerate (iterate from current_props) cases with the same flow. New endpoint GET /api/v1/workspace/v2/blocks/[name]/regenerate. (2) reorder_landing_sections({ workspace_id, new_order: string[] }) — purely mechanical reorder of Blueprint.landing.sections; new_order is the full ordered array of section types ([\"hero\", \"services-grid\", \"about\", \"mid-cta\", \"faq\"]); validates the multiset matches current sections (no add/remove); rejects empty new_order, duplicates, missing types, unknown types. New endpoint POST /api/v1/workspace/v2/landing/reorder. Operator says \"move FAQ to bottom\" → agent computes new_order → server reorders + re-renders. (3) upload_workspace_image({ workspace_id, slot, file_name, content_type, image_data_b64 }) — uploads a base64-encoded image to Vercel Blob and applies the URL to one of two slots: 'logo' (organizations.theme.logoUrl, surfaces in header/footer/og-image) or 'hero_background' (Blueprint.landing.sections[hero].imageUrl + landing re-render). 5 MB max, allowed types image/png|jpeg|webp|svg+xml|gif. Sanitized blob path with random uuid suffix so re-uploads don't overwrite previous URLs. New endpoint POST /api/v1/workspace/v2/images. WHAT SHIPS BESIDES THE TOOLS: 22 new unit tests (6 regenerate context assembly, 6 reorder validation, 11 upload validation + path sanitization). Pure-function-first design — DB integration left for the v1.10+ harness; what's tested here is the security-relevant + correctness-relevant logic that lives outside any DB. Test count: 2366 (was 2344; +22). NO migrations, NO env vars, NO new dependencies (Vercel Blob already used for portal documents). Backend redeploy auto-triggers via main-branch push. v1.11+ candidates: customize_theme advanced presets (palette generation from logo / industry-aware mood), additional image slots (og_image, gallery), section delete/insert (currently reorder is the only structural change supported).",
104
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.9.0: BEHAVIORAL CONTRACT TESTS — kills the handler-execution-state-drift bug class structurally. Three bugs in 7 days hit the same shape: a server-side function `throw new Error()`s for an edge user/workspace state (no users row, no Stripe customer, free tier, empty form_fields), and the throw cascades through the SSR boundary as 'This page couldn't load.' WHAT SHIPS: (1) static analysis test (tests/unit/contract-no-uncaught-throws.spec.ts) that scans designated directories — lib/billing/**, lib/auth/**, lib/page-blocks/persist.ts, app/(dashboard)/** — for `throw new Error()` patterns. Every throw must EITHER be wrapped in try/catch OR carry a `// contract:throw-ok: <reason>` annotation. CI fails if any throw is unannotated. (2) 34 existing throws audited and annotated with semantic reasons (Stripe API errors, programmer-error sentinels, deployment config, form-submit error UX, etc.). Going forward, every NEW throw added to scoped directories must be annotated explicitly. (3) Behavioral runtime test (tests/unit/contract-booking-form-fields.spec.ts) for the v1.4.2 bug: 5 tests asserting mergeBookingFormFields always prepends standard fullName + email regardless of LLM input — preserves order, dedupes redundant LLM-supplied standards, sets required=true. mergeBookingFormFields exported from lib/page-blocks/persist.ts for testability. (4) docs/CONTRACTS.md documenting the rule, annotation pattern, scoped directories, when NOT to throw, how to add a new contract test. The MCP package itself didn't change in v1.9.0 — version bumps coupled with backend so they redeploy together. Test count: 2344 (was 2338; +1 static check + 5 booking contract tests). v1.10 candidate: integration test harness so we can write contract tests for the v1.7.3 + v1.8.1 fixes (currently documented in CONTRACTS.md but not yet runtime-asserted).",
105
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.8.1: HOTFIX for /settings/billing crashing when free-tier users click 'Manage subscription'. createBillingPortalSessionAction threw 'No Stripe customer is associated with this account' when the user had never completed a Stripe checkout — the throw cascaded through the SSR boundary and produced 'This page couldn't load — A server error occurred'. Two-layer fix: (1) UI conditionally hides 'Manage subscription' button for free tier, shows 'Upgrade' link instead. Free-tier operators see the right CTA. (2) The action gracefully redirects to /settings/billing?upgrade=needed instead of throwing — defense in depth in case a stale page state, direct form POST, or admin-token edge case still hits the action with no customer. New banner on /settings/billing surfaces the redirect reason ('No active subscription to manage yet — upgrade to manage'). No code changes to the MCP package itself; version bump is coupled with the backend redeploy.",
106
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.8.0: CUSTOM DOMAINS for paying tiers (Growth $29 / Scale $99). Operators on a paid plan can route their own hostname (joescuts.com, ironandoak.ca, bookings.coastlinemusic.ca, etc.) to their workspace; Vercel auto-provisions SSL via Let's Encrypt; subdomain path stays the default for free tier. WHAT SHIPS: (1) workspace_domains table (migration 0039, applied to prod) — workspace_id, hostname (unique among non-removed rows), status walk pending→verified→failed→removed, verification_record jsonb, vercel_domain_id, is_primary, with three indexes including a hot-path lookup for proxy.ts host→workspace resolution. (2) lib/integrations/vercel-domains.ts: thin Vercel Domains API wrapper (vercelAddDomain, vercelGetDomainConfig, vercelRemoveDomain, isValidHostname, isVercelConfigured). Requires VERCEL_TOKEN + VERCEL_PROJECT_ID env vars; returns 'vercel_not_configured' cleanly when missing. (3) lib/domains/store.ts: addCustomDomain (validate + Vercel register + persist + DNS instructions), verifyCustomDomain (Vercel /config check + state transition), listCustomDomainsForWorkspace, removeCustomDomain (mark removed in DB first, then best-effort Vercel cleanup), resolveWorkspaceForCustomDomain (the proxy.ts hot path). (4) Single REST endpoint POST /api/v1/domains dispatching on `op` field (add/verify/list/remove) with workspace bearer auth + tier gate (free returns 402 upgrade_required) + Vercel-configured gate (503 vercel_not_configured) + try/catch wrapping for JSON 500 shape. (5) Four new MCP tools: add_custom_domain, verify_domain, list_workspace_domains, remove_workspace_domain. Free-tier MCP calls surface the 402 with upgrade CTA. (6) /api/v1/public/domain (the proxy hostname-to-workspace resolver) now checks workspace_domains FIRST, then falls back to legacy organizations.settings.customDomain, then to subdomain extraction. (7) Welcome email v2: tier-conditional block between Next-Steps and Discord CTA — free tier sees 'Want your own domain?' upgrade pitch; paid tier sees 'Custom domain ready — ask Claude to add_custom_domain'. WelcomeEmailRequest now accepts optional `tier` param, validateWelcomeRequest passes it through. MCP loads 98 tools (was 94; +4 domain tools). ⚠️ BACKEND REDEPLOY REQUIRED. Migration 0039 already applied. SET VERCEL_TOKEN + VERCEL_PROJECT_ID (+ optional VERCEL_TEAM_ID) on Vercel project env vars to enable the feature; without these, MCP tools return vercel_not_configured.",
107
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.7.3: HOTFIX for /dashboard crashing with 'This page couldn't load' when a NextAuth session exists but the users table has no matching row. Symptom: operator signs into app.seldonframe.com (NextAuth + Resend or Google OAuth), navigates to /dashboard, server returns 500 with `Error: Unauthorized`. Logs show `userId: <real uuid>, userOrgId: <real uuid>, membershipIdsRaw: []`. Root cause: lib/billing/orgs.ts::getBillingUserById threw 'Unauthorized' when the session user.id had no row in the users table (NextAuth Drizzle-adapter race? imported user from another deployment? failed insert?). The throw cascaded through dashboard layout → page → SSR boundary → frontend error. Fix: getBillingUserById now returns a synthesized empty record (id=session-user-id, orgId=null, no plan/sub) instead of throwing. listManagedOrganizations + getOwnedWorkspaceCount + dashboard data-loaders return []/0 for these synthesized users so the dashboard renders an empty 'no workspaces yet' state instead of crashing. Two callers were updated to handle the new nullable user.orgId — listManagedOrganizations builds OR conditions defensively, and the orgId-membership lookup falls back to ownerId/parentUserId paths when user.orgId is null. The MCP package itself didn't change in v1.7.3 — version bumps are coupled across backend + npm by convention so they redeploy together. ⚠️ BACKEND REDEPLOY REQUIRED. v1.8 candidate: auto-create users row on first session OR auto-claim anonymous workspaces by matching operator email when a user signs in (so users see workspaces they created via MCP).",
108
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.7.2: HOTFIX for the device-flow approve handler crashing on production environments without ENCRYPTION_KEY set. Symptom (Iron & Oak test): operator clicks Yes on the browser approval page, server logs `Missing ENCRYPTION_KEY`, returns empty 500, browser shows `Failed to execute 'json' on 'Response': Unexpected end of JSON input`, MCP polling loop keeps returning pending until the 5-min budget exhausts. Three fixes: (1) approveDeviceAuth now wraps encryptValue in try/catch; on failure (missing ENCRYPTION_KEY), stores the bearer raw + logs a warning telling operators to set the env var. The bearer is still single-shot (atomic claim in checkDeviceAuth) + still scoped via atok+status+claimedAt semantics — encryption-at-rest was defense-in-depth, not the primary security. checkDeviceAuth's existing decrypt-or-passthrough logic handles both cases. (2) All four /api/v1/auth/* endpoints (initiate, approve, reject, check) wrapped in try/catch returning JSON 500 on unexpected errors instead of empty bodies — keeps the browser approval page + MCP polling loop showing readable error messages. (3) checkDeviceAuth's decrypt fallback now logs the failure reason. No schema changes. ⚠️ BACKEND REDEPLOY REQUIRED. Existing pending atoks from v1.7.0/v1.7.1 attempts will still expire in 5min — run connect_workspace again to get a fresh atok.",
109
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.7.1: HOTFIX for connect_workspace crashing with `VERSION is not defined`. The v1.7.0 connect_workspace handler builds a User-Agent header on its anonymous /auth/initiate + /auth/check fetches (those bypass api() because they're pre-bearer) and references the module-scope VERSION constant from welcome.js. tools.js wasn't actually importing VERSION — earlier drafts assumed it leaked via client.js's transitive import; it doesn't. The first real connect_workspace call after a fresh MCP session crashed with `VERSION is not defined` before sending the request. v1.7.1 directly imports VERSION from welcome.js alongside FIRST_CALL_BANNER. No other code changes; same 94 tools.",
110
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.7.0: MAGIC-LINK DEVICE-FLOW AUTH. Operators can now connect a fresh IDE/device (Claude Code on a new laptop, Cursor on a different machine, etc.) to an existing SeldonFrame workspace with one email click — no token copy-paste, no re-running create_workspace, no friction. WHAT SHIPS: (1) device_auth_requests table (migration 0038, applied to prod) — atok (single-use 256-bit URL-safe random), workspace_id, email, device_label, status walk (pending → approved/rejected/expired), expires_at (5-min TTL), issuedTokenRaw (encrypted at rest, one-shot cleared on claim). (2) lib/auth/device-auth.ts: initiateDeviceAuth (mints atok + writes pending row), approveDeviceAuth (marks approved + mints workspace bearer via existing mintWorkspaceToken with 7-day TTL), rejectDeviceAuth, checkDeviceAuth (atomic claim — concurrent polls race; only one wins; raw token cleared after claim), lookupDeviceAuthForApprovalPage (read-only, for the browser page render). (3) lib/emails/device-auth.ts: rendered HTML + plain-text email template, sender via existing Resend infrastructure with verified welcome@seldonframe.com. (4) Four anonymous API endpoints — POST /api/v1/auth/initiate (rate-limited 10/hr/IP, sends email + returns atok), POST /api/v1/auth/approve (called by browser approval page on Yes), POST /api/v1/auth/reject (called on No), GET /api/v1/auth/check?atok=... (polled by the MCP server). (5) Browser approval page at /auth?atok=... — server-component shell + client-component Yes/No buttons. Renders workspace name + device label + email so the operator can verify they're authorizing the right device. Pre-renders different states for already-approved / rejected / expired / claimed. (6) New MCP tool connect_workspace(workspace_slug, email, device_label?). Calls initiate, then polls /api/v1/auth/check every 2s for up to 5 minutes. On approval, stores the bearer locally + sets as default workspace. Auto-detects device label from os.hostname() unless overridden. (7) Welcome message updated to teach the new tool — `create_workspace_v2` for new workspaces, `connect_workspace` for existing ones from new devices. MCP loads 94 tools (was 93; +1). ⚠️ BACKEND REDEPLOY REQUIRED. Migration 0038 already applied to prod by the script in this commit. RESEND_API_KEY env var must be set (already is — same one welcome emails use).",
111
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.6.0: BRAIN LAYER (the compounding moat). Two-layer file-tree of markdown notes implementing the Karpathy LLM-Wiki pattern: Layer 1 is per-workspace (notes about THIS workspace's customers, voice, pipeline patterns, learnings); Layer 2 is anonymized cross-workspace patterns the weekly cron promotes when 3+ workspaces independently observe the same thing with confidence ≥ 0.7. Each note carries a Bayesian-smoothed confidence score `(wins + 1) / (uses + 2)` so the system self-prunes bad entries (confidence drops, weekly cron archives) and self-promotes good ones (workspace pattern hits threshold, cron creates global note). The IDE agent reads relevant notes before generating blocks; the brain compounds across every workspace interaction. WHAT SHIPS: (1) brain_notes table (migration 0037, applied to prod) with org_id-NULL-for-global discriminator, scope text, path file-tree key, body markdown, metadata jsonb, confidence/uses/wins counters, and indexes for prefix listing + promotion + pruning. (2) lib/brain/store.ts CRUD: readBrainNote (ticks uses + last_used_at), listBrainDir (prefix-filtered, returns body previews), writeBrainNote (upsert, preserves confidence), appendToBrainNote (prepends dated paragraphs, 8KB cap), deleteBrainNote, computeConfidence, markBrainOutcome (win/loss feedback), findPromotionCandidates (uses ≥ 10, confidence ≥ 0.7, ≥ 3 workspaces), findPruneCandidates (confidence < 0.3, uses ≥ 10). (3) Single REST endpoint POST /api/v1/brain dispatching on `op` field (read|list|write|append|delete|list_patterns) with workspace bearer auth + path-traversal guards. (4) Four new MCP tools: read_brain_path, list_brain_dir, write_brain_note (with append flag), list_brain_patterns. Welcome message updated to teach the brain pattern. (5) Per-interaction triggers: submitPublicBookingAction appends to pipeline/booked-appointments.md when a booking confirms; the public intake POST appends a non-PII summary (answered/skipped field counts + low-risk values) to intake/recent-leads.md. Best-effort — failures don't block the user-facing response. (6) Weekly cron at /api/cron/brain-promote scheduled Sundays 05:00 UTC in vercel.json. Promotes high-confidence workspace notes to layer 2 (synthesized as bullet-list aggregations for v1.6; LLM-based synthesis lands in v1.7); prunes low-confidence workspace notes. Both ops idempotent. (7) create_workspace_v2 now pre-fetches up to 10 layer-2 patterns matching the resolved personality vertical and returns them inline as v2.brain_patterns so the IDE agent has cross-workspace context without a second round-trip. The brain compounds: each new workspace's interactions feed the brain, the cron promotes patterns weekly, the next workspace's IDE agent reads richer context, output quality improves over time WITHOUT code changes — the Karpathy compounding moat. ⚠️ BACKEND REDEPLOY REQUIRED. Migration 0037 already applied to prod. Set CRON_SECRET env var on Vercel if not already set.",
112
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.5.1: HOTFIX — five fixes from the Coastline Music workspace test. (1) BLOCKING — apply-0031-prod.mjs applied the missing portal_documents migration that was crashing every contact-detail page load with `relation \"portal_documents\" does not exist`. Operators can now view lead records again. (2) Lucide icon allowlist now ENFORCED — pre-1.5.1 the LLM could pick icon names like \"piano\", \"microphone\", \"wood_oven\" that the renderer didn't have, so all those services-grid cards rendered with the same fallback icon (visible on Coastline Music + Cinder & Salt). New ICON_NAMES export from lucide-icons.ts (derived from ICON_PATHS so it stays in sync); new services validator rejects unknown icons and returns the full allowlist in the error so the LLM can self-correct on retry. (3) Smart pipeline-stage selection on booking — pre-1.5.1 every booking landed at the FIRST pipeline stage (\"Inquiry\" / \"Lead\"), forcing operators to manually move every deal up a stage. Now the deal-insert path matches stages whose names contain booked / scheduled / trial / appointment / consultation / reservation and uses that stage instead, falling back to first stage when no match exists. Personality-generated pipelines that declare \"Trial Lesson Booked\" / \"Estimate Scheduled\" automatically get the right behavior. New stage_match telemetry tag (\"smart\" vs \"first_stage_fallback\") + available_stages array in the public_booking_deal_created log. (4) FAQ headline centering bulletproofed with !important + max-width: none override on .sf-faq > .sf-faq__headline — the .sf-faq > * { max-width: 48rem } rule was constraining the H2 narrower than the page, making centered text appear left-anchored. (5) Hero subhead text-align: center pinned in base CSS (was inheriting parent text-align which varied by overlay), plus eyebrow pill background hardened to rgba(0,0,0,0.45) with white border + backdrop-filter blur on hero-with-image so the eyebrow text stays readable against any photo. ⚠️ BACKEND REDEPLOY REQUIRED. Migration 0031 already applied to prod by the same script that ships in this commit.",
113
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.5.0: BLOCK CODEGEN — kills the prop-schema-drift bug class structurally. Pre-1.5 every block's prop schema lived in TWO places: the SKILL.md frontmatter (LLM-readable YAML, the source of truth for the agent's generation prompt) AND lib/page-blocks/registry.ts (runtime Zod, the source of truth for server validation). Editing one without the other was undetectable until a runtime failure surfaced it; the Cinder & Salt booking-form bug (v1.4.2 hotfix) was one such failure. v1.5 makes drift structurally impossible: SKILL.md frontmatter is the single source of truth, and `pnpm blocks:emit` parses each block's frontmatter and generates packages/crm/src/blocks/<name>/__generated__/block.ts containing the Zod schema, the inferred TypeScript Props type, and the block metadata constants (name, version, surface, sectionType, description). The registry imports from these generated files; the toSection mapping + deterministic copy validators stay handwritten because they're logic, not schema. CI gate: `pnpm blocks:emit:check` re-runs the emitter and exits non-zero if any __generated__/block.ts is stale relative to its SKILL.md — a new unit test (tests/unit/blocks-codegen-staleness.spec.ts) makes every PR fail if anyone edits a SKILL.md without regenerating. Codegen handles all the types we use across 7 blocks: string/number/boolean with min/max bounds, enum, object with nested properties, array of objects with min_items/max_items, tuple (booking weekly_availability uses [openHour, closeHour] pairs), nullable, optional/required. Adds `yaml` devDependency for frontmatter parsing. Documented in packages/crm/src/blocks/README.md. ⚠️ BACKEND REDEPLOY REQUIRED.",
114
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.4.2: HOTFIX for the booking-form regression introduced by v1.4.1. The v2 booking persist path replaced Blueprint.booking.formFields wholesale with whatever the LLM generated — wiping out the standard `fullName` + `email` fields v1's bootstrap had set. Result: every v2 workspace shipped a booking form with NO name/email inputs, and submitPublicBookingAction rejected every submit with `missing_required_field fullName_present:false email_present:false`. Confirmed on Cinder & Salt (Hamilton wood-fired pizza) where the form had Party Size + Occasion + Allergies but no name/email. v1.4.2 fixes: (1) New mergeBookingFormFields helper in lib/page-blocks/persist.ts — server ALWAYS prepends fullName + email, dedupes against any LLM-provided field with the same id; LLM-provided extras (party_size, dog_name, service_address, etc.) keep working unchanged. (2) booking SKILL.md updated — voice rules now correctly say 'EXTRAS only, server adds standard name+email' instead of the wrong 'renderer adds them automatically'. (3) Backfill scripts shipped: backfill-booking-form-fields.mjs (47 prod workspaces had their blueprint.booking.formFields patched to include the standard fields) + scripts/rerender-all-bookings-v142.ts (re-renders bookings.content_html for 63 booking templates so the live booking pages actually show the form fields). Both scripts already executed against prod. ⚠️ BACKEND REDEPLOY REQUIRED.",
115
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.4.1: COMPLETE v2 BLOCK COVERAGE — v1.4 shipped 3 block primitives (hero, services, faq) and proved the architecture; v1.4.1 ships the remaining 4 (about, faq, cta, booking, intake) so v2 owns every operator-facing surface end-to-end. Validated against the Pawsh & Polish mobile-dog-grooming workspace test, where the v2 hero (\"One Dog at a Time, In Your Driveway. 4.9★, 280+ Reviews.\") and the niche-aware FAQ (\"Where does the van park?\", \"What if my dog is nervous?\") proved that LLM-in-IDE generation produces visibly better output than the v1 personality system ever did for a long-tail vertical. Now extending to about + cta (landing-section surface), booking (calendar surface — title, duration, location_kind, weekly availability hours, optional form fields, validators against the v1 'Free consultation / 30-minute conversation' template leak), and intake (form surface — title, questions with proper field types per vertical, completion message, validator against the 'Tell us about your project' template leak). New BlockSurface discriminator routes persistence — landing-section blocks mutate Blueprint.landing.sections + re-render via renderGeneralServiceV1; booking blocks mutate Blueprint.booking + bookings table metadata + re-render via renderCalcomMonthV1; intake blocks mutate Blueprint.intake + intakeForms table fields + re-render via renderFormbricksStackV1. Welcome message updated to teach Promise.all parallelism (sequential 7-block generation blows the 60s latency budget; parallel keeps it well under). Plus: FAQ + services + about section headlines reinforced with explicit centering CSS (display:block + margin:auto + width:100% + text-align:center) after operator feedback that the headline rendered left-aligned in some viewports. Reuses the same block_instances table (no migration needed). ⚠️ BACKEND REDEPLOY REQUIRED.",
116
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.4.0: ARCHITECTURAL SHIFT — block-as-skill primitives + MCP-native workspace creation (the Karpathy-inflected 'thin harness, fat skills' move). Pre-1.4 the SF backend generated all landing-page copy server-side from a hardcoded personality system + JSON templates + 6+ deterministic transforms. Every new niche surfaced a fresh layer-mismatch bug (10 v1.3.x releases shipped fixes for the same bug class). v1.4.0 inverts the architecture for the highest-stakes copy surfaces — hero, services, faq — by moving block GENERATION out of the SF backend and into the operator's IDE agent (Claude Code, Cursor, Windsurf, etc.). Each block now lives as a single-source-of-truth folder under packages/crm/src/blocks/<name>/SKILL.md containing the prop schema (YAML frontmatter) + generation prompt + voice rules + worked examples + validator definitions (markdown body). Five new MCP tools enable the v2 flow: (1) create_workspace_v2 — bootstraps the workspace via the v1 orchestrator, returns recommended_blocks + workspace context for the IDE agent's LLM; (2) list_blocks — discovers v2 page primitives; (3) get_block_skill — returns raw SKILL.md the IDE agent reads; (4) persist_block — validates props against the block's Zod schema + deterministic copy-quality validators (headline_quantified, distinct_icons, no_coaching_leak, no_throat_clearing, etc.), maps props onto the existing LandingSection shape, re-renders the full landing via the v1 renderer, persists block_instances row + landing_pages update; (5) complete_workspace_v2 — reports which blocks landed vs. fell back to v1 defaults. Forever-frozen edits live in block_instances.customizations (jsonb append-only) so operator edits via 'customize' calls survive workspace re-rolls. v1 (create_full_workspace) still owns booking, intake, about, theme, pipeline, CRM — and remains the safe fallback for any caller that doesn't know about v2 yet. ⚠️ BACKEND REDEPLOY REQUIRED + apply migration 0036_block_instances.sql in production. v1.5 will land the SKILL.md → __generated__/ codegen so the prop schema lives in exactly one place.",
117
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.3.5: Three Karpathy-flavored root-cause fixes shipped in one cycle, all motivated by what the Iron & Oak Barbershop test surfaced. (FIX D — booking + intake 400 on subdomains) The C4 booking client and C5 intake client both extracted orgSlug from window.location.pathname, expecting /book/<slug>/<bookingSlug> or /forms/<slug>/<formSlug>. On a workspace subdomain (<slug>.app.seldonframe.com) the proxy rewrites the path server-side but the BROWSER URL stays at /book or /intake — clients saw no slug, posted orgSlug='', and every visitor's submission rejected with missing_required_field 400. Server-side defense in depth: new resolveWorkspaceSlugFromHostHeader helper in lib/workspace/host-to-slug.ts mirrors proxy.ts logic; both public POST routes now derive orgSlug from the Host header when the body value is missing. Client-side defense in depth: both renderers fall back to `window.location.hostname.split('.')[0]` when pathParts[1] is empty. Plus public_intake_succeeded/_rejected logs (parity with v1.3.3 booking observability) so the intake funnel is visible end-to-end. New slug_source tag on success logs lets us measure how often the host fallback rescues a request. (FIX E — pipeline validator's HTML-entity blind spot) The 'BUSINESS NAME NOT IN HTML' check in pipeline-validator.ts did a literal substring search on the rendered HTML. Business names with HTML-special chars ('Iron & Oak Barbershop', \"Joe's Plumbing\", '<3 Salon') get entity-encoded by the renderer ('Iron &amp; Oak Barbershop'), so the literal check missed them and emitted false-positive blocking errors even though the name WAS rendered correctly. New htmlContainsText helper tries the literal needle, then its HTML-escaped form, then the &apos; variant — same logic for the first-offering fallback. Validator was wrong, not the rendering pipeline. (FIX F — model upgrade) Default ANTHROPIC_MODEL is now claude-opus-4-7 (was claude-sonnet-4-20250514). Personality generation is a once-per-niche cost — the cache amortizes Opus across every future workspace in that vertical, so the per-workspace blended cost stays near-zero while cold-call quality improves meaningfully on long-tail niches (pet grooming, accounting, voiceover, etc.). Three-tier fallback chain: Opus 4.7 (primary) → Sonnet 4.5 / claude-sonnet-4-5-20250929 (secondary) → Haiku 3.5 (tertiary). Each tier overridable via ANTHROPIC_MODEL / ANTHROPIC_MODEL_FALLBACK / ANTHROPIC_MODEL_TERTIARY env vars; chain walks down on any 404 so a single model retirement never takes the system offline. ⚠️ BACKEND REDEPLOY REQUIRED. No env vars to set on Vercel — Opus is the new default; if cost is a concern set ANTHROPIC_MODEL=claude-sonnet-4-5-20250929 to revert.",
118
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.3.4: ROOT-CAUSE the last 3 'wrong default everywhere' bugs from the Iron & Oak Barbershop test by extending CRMPersonality with two new LLM-populated fields + closing the booking → CRM kanban loop. (FIX A — booking metadata personalization) Pre-1.3.4 the booking calendar showed 'Free consultation / 30-minute conversation' for ANY niche not in personalityBookingDefaults (only 7 hardcoded verticals — barbershop, photography, accounting, tutoring, etc. all leaked the general.json template default). New optional CRMPersonality.booking field { title, description, duration_minutes, location_kind } populated by the LLM via the personality generator's prompt. createAnonymousWorkspace applies it as the highest-priority source (LLM > personalityBookingDefaults > JSON template). location_kind enum mirrors the blueprint's eventType.location.kind exactly (on-site-business / on-site-customer / phone / video / hybrid) so there's zero translation. (FIX B — hero image relevance) Pre-1.3.4 every workspace whose vertical wasn't in the curated IMAGES map fell back to GENERAL_IMAGES (hand-picked CONTRACTOR photos — bizarre for nail salons, tutoring, barbershops). New optional CRMPersonality.images.hero_query field — free-text Unsplash search query the LLM picks per-niche ('barbershop interior', 'math tutoring student', 'nail salon manicure'). New resolveHeroImageUrlForQuery function tries the official Unsplash API first when UNSPLASH_ACCESS_KEY is set, then falls back to source.unsplash.com keyless redirect. applyPersonalityImagesToSchema is now async and prefers LLM > curated > skip. (FIX C — bookings appear in CRM kanban) Pre-1.3.4 submitPublicBookingAction inserted into bookings + activities + contacts but NEVER into deals — so the visitor's confirmed appointment never appeared in /deals where operators actually triage their pipeline. v1.3.4 calls ensureDefaultPipelineForOrg (lazy-seeds when missing) and INSERTs a deal at the first stage on every successful free booking, with customFields.bookingId for reconciliation against future booking events. Wrapped in try/catch + structured public_booking_deal_created/_failed events — a deal-insert failure does NOT roll back the booking. (VALIDATOR) +3 new checks: hero_image_loadable (asserts the rendered URL belongs to a known Unsplash host pattern + has sizing params), booking_title_personalized (asserts the booking title doesn't contain 'Free consultation' / '30-minute conversation' AND uses the personality's title if declared), booking_to_deal_pipeline_ready (asserts the org's first pipeline stage has a non-empty name so deal-insertion lands in a renderable kanban column). ⚠️ BACKEND REDEPLOY REQUIRED. Optional: set UNSPLASH_ACCESS_KEY on Vercel to use the official API for hero images instead of the keyless source.unsplash.com redirect.",
119
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.3.3: ROOT CAUSE for the recurring 'wrong personality everywhere except landing' bug pattern + booking observability + per-service enrichment. (FIX 1) The booking POST /api/v1/public/bookings was returning 400 with literally NO LOGS ('No logs found for this request' in Vercel UI). Every reject path now emits a single-line structured JSON event (event: public_booking_rejected, reason: <stable_id>, ...context) so we can finally see WHICH validation is rejecting bookings. submitPublicBookingAction got the same treatment: every throw now logs reason + booking context (org_slug, weekday_in_workspace_tz, slot_local_hhmm, day_start, day_end, ...). The next time someone hits 'Couldn't book that time', the Vercel log will name the exact cause. (FIX 2 — REAL ROOT CAUSE) The LLM-generated CRMPersonality from create-full.ts was being THROWN AWAY by createAnonymousWorkspace — which called selectCRMPersonality (keyword-based) internally, ignoring the resolved personality. So booking template + intake form + landing seed all used the keyword-fallback personality (e.g. 'general' for nail salons → 'Get in Touch' intake title, 'New Lead → Quoted → ...' pipeline) while only the post-seed defensive override updated settings.crmPersonality with the LLM personality. Result: rendered intake HTML never had the personality title; that's why intake_title_rendered_html: FAIL kept appearing. Fix: createAnonymousWorkspace now accepts an opts.personality parameter; create-full.ts passes the LLM-resolved personality through. Booking template + intake form + landing seed all use the LLM personality from the start. (FIX 3a) Light mode is now the default fallback when personality.theme.mode is unset — premium verticals must opt in to dark via theme.mode='dark'. Previously the fallback used the BusinessType heuristic which routed saas/agency to cinematic (dark) by default. (FIX 3d/e) New optional services_enrichment field on CRMPersonality: array with one entry per operator service { service_name, description, icon }. The LLM is prompted to populate it with customer-facing descriptions + distinct icons per service from a known allowlist. Pipeline merges descriptions onto soul.offerings (so service cards show body copy) and overrides applyServiceIconsFromPersonality (so each card has a distinct LLM-picked icon instead of the keyword classifier's homogenous defaults). ⚠️ BACKEND REDEPLOY REQUIRED. Run a niche test post-deploy: every personality_resolved log line now also tells you whether bookings persist (public_booking_succeeded) or why they reject (public_booking_rejected with reason).",
120
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.3.2: 3 fixes from the Bright Futures Tutoring test. (FIX 1, CRITICAL) Hardcoded model 'claude-3-5-sonnet-latest' returns 404 from the Anthropic API; every LLM personality call fell through to keyword fallback (coaching for everything that wasn't a known vertical). Replaced with ANTHROPIC_MODEL env var (default: claude-sonnet-4-20250514) + ANTHROPIC_MODEL_FALLBACK (default: claude-3-5-haiku-20241022) + a 404-detection retry that swaps to the haiku fallback. Model id is logged in the personality_resolved event so prod observability shows which model is actually serving. Zero-deploy rotation: when Anthropic retires another model, set the env var on Vercel and the next request picks it up. (FIX 2) Booking confirmation rejected every valid slot with 'Selected slot is no longer available'. Root cause: the strict slot inclusion check compared format strings produced from server-local time (Vercel = UTC), but the client picked the slot in the workspace's timezone (e.g. 4pm Vancouver). The UTC ISO landed on the server as 23:00 UTC, formatted as 'YYYY-MM-DDT23:00', which never appeared in the server-generated slot list (UTC 09:00–16:30). Replaced with timezone-aware validation: derive workspace-local components via Intl.DateTimeFormat.formatToParts (new partsInTimezone helper), check the day-of-week is enabled, the time-of-day is within business hours, the slot aligns to a 15-minute boundary, and there's no overlap with existing bookings. Works regardless of where the server runs. (FIX 3) Light mode is now the default. Added theme.mode ('light'|'dark') to CRMPersonality. Populated for the 7 hardcoded verticals: medspa+agency stay dark/cinematic; everything else (general, hvac, dental, legal, coaching) renders in light/clean. The LLM personality generator's prompt now instructs the model to choose 'light' for trades/tutoring/professional-services and 'dark' only for premium/luxury/aesthetic verticals. seedLandingFromSoul reads personality.theme.mode and selects the cinematic vs clean PagePersonality accordingly. ⚠️ BACKEND REDEPLOY REQUIRED + set ANTHROPIC_MODEL=claude-sonnet-4-20250514 in Vercel.",
121
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.3.1: 3 pipeline integration bugs fixed at the consumer side. The personality system, validator, cache all worked correctly — but downstream pipeline steps consumed the data from the wrong field shape (or didn't consume it at all). (BUG 1) booking metadata.availability was being written as {weekdays:[],startHour,endHour} but the booking actions read {monday:{enabled,start,end},...} (full day names). The shape mismatch silently fell back to defaults that happened to give Mon-Fri 9-17, masking the bug for happy paths but breaking per-personality booking hours (HVAC's Mon-Sat 7-19 was never honored), the React fallback path, and the validator. Fixed by sourcing canonical schedule from blueprint.booking.availability.weekly via buildAppointmentAvailabilityFromBlueprint. (BUG 2) personality.intake.title (added v1.1.9) only updated the DB row's name column; the rendered Formbricks HTML reads blueprint.intake.title which still had the JSON template's 'Tell us about your project'. Fixed by mutating seedBlueprint.intake.title + intake.questions from personality.intake + intakeFields in createAnonymousWorkspace, parallel to the booking eventType mutation. (BUG 3) getPersonalityImages returned null for unknown verticals — for v1.3.0 LLM-generated personalities (vertical='roofing'/'pet-grooming'/etc) the IMAGES map had no entry, so workspaces shipped text-only heroes. Fixed by falling back to the GENERAL_IMAGES bundle so every workspace gets industry-neutral photography. (VALIDATOR) +4 new checks: booking_availability_enabled_days (canonical shape), booking_renderer_data_island, hero_background_image, intake_title_rendered_html — every regression at any of the 3 integration points now surfaces in Vercel function logs. ⚠️ BACKEND REDEPLOY REQUIRED.",
122
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.3.0: LLM-generated personalities (the real Karpathy move). Hardcoding 7 personalities + a 'general' fallback (v1.2.0) doesn't scale to the long tail — pet grooming, photography, accounting, tutoring, and a hundred other niches will each fall into 'general' and produce mediocre output. v1.3.0: model GENERATES the personality, validator GATES the quality, cache SCALES the cost. Three-layer resolve: (1) cache: SELECT from new personality_cache table by stable business_type_key derived from services+description (e.g. 'roofing', 'pet-grooming', 'wedding-photography'). Hit → return cached schema, increment usage_count, zero LLM cost. (2) LLM: Claude generates a custom CRMPersonality for THIS specific business via a few-shot prompt with 4 hardcoded personalities as examples. Output validated by checkPersonalityCompleteness (intake.title, ≥3 FAQs, exactly 4 trust badges, ≥4 pipeline stages, etc.). Retry once on validation fail with errors appended to prompt. On success, INSERT into cache (ON CONFLICT DO NOTHING handles parallel-create races). (3) Fallback: existing keyword-based selectCRMPersonality if LLM unavailable / fails twice. Hardcoded personalities (general, hvac, dental, legal, agency, coaching, medspa) become SEED CACHE rows + few-shot examples — they're the warm starting point, not the final system. Cost model: ~$0.01 first workspace per niche; subsequent workspaces $0. Quality model: every cached schema passed completeness check. Anti-fragile: gets better as Claude gets better, no code changes. New observability: workspace_created_full event tags personality_source (cache/llm/fallback) + personality_cache_key for cache-hit-ratio analytics. ⚠️ BACKEND REDEPLOY REQUIRED + run migration 0035_personality_cache.sql.",
123
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.2.0: complete the default personality + add CTA defense layers. The Ironclad Roofing demo exposed the gap that v1.1.9 didn't catch: roofing fell through to coaching personality, both CTAs went to /intake, all 6 service cards collapsed to wrench, no hero image. Root cause: DEFAULT_PERSONALITY was COACHING (with coaching pipeline + 'Discovery call' CTAs); BUSINESS_TYPE_FALLBACK rerouted local_service+professional_service to coaching; trade keywords (roof/storm/gutter/siding/window/deck/etc) weren't in the icon RULES; and the v1.1.9 CTA hardcoding only ran in the soul-driven path, not in the template-load path. (1) NEW general personality — industry-neutral (Customer/Job/Activity, 'New Lead → Quoted → Approved → Scheduled → In Progress → Completed' pipeline, 'Book a free quote →' CTA, steel-blue accent #0e7490). Has its own content_templates, intake.title, image bundle. Made the new DEFAULT_PERSONALITY. BUSINESS_TYPE_FALLBACK rerouted local_service+professional_service from coaching → general. (2) +13 trade icons (cloud_lightning, columns, search, app_window, square_stack, hammer, paintbrush, layers, fence, blocks, thermometer, square, ruler) +20 keyword RULES (storm damage→cloud_lightning, tarping→shield, roof/shingle→home, gutter→droplets, siding→columns, deck→square_stack, renovation→hammer, etc.) ordered specific-first so 'roof repair' hits home before generic 'repair'→wrench. (3) CTA HARDCODING DEFENSE-IN-DEPTH — now applied at FOUR layers: (a) skills/templates/general.json + hvac.json fixed at source, (b) buildBlueprintForWorkspace normalizes hero/mid-cta CTAs immediately after pickTemplate, (c) seedLandingFromSoul applyResolvedContentToActions overrides at schema level (v1.1.9), (d) personality content_templates carry the labels. Even if one layer misses, the next catches. (4) +9 trade keywords in BusinessType classifier (roof, storm damage, gutter, siding, fencing, decking, drywall, stucco, masonry) so trade workspaces classify as local_service instead of professional_service. ⚠️ BACKEND REDEPLOY REQUIRED — npm package is just tool definitions.",
124
6
  "type": "module",
125
7
  "bin": {
126
8
  "seldonframe-mcp": "src/index.js"
@@ -148,9 +30,22 @@
148
30
  "agents",
149
31
  "business-os",
150
32
  "claude-code",
151
- "crm"
33
+ "crm",
34
+ "gohighlevel-alternative",
35
+ "gohighlevel",
36
+ "agency",
37
+ "whitelabel",
38
+ "partner-agency",
39
+ "booking",
40
+ "intake-form",
41
+ "landing-page",
42
+ "local-service-business",
43
+ "sms",
44
+ "twilio",
45
+ "ai-receptionist",
46
+ "ai-chatbot",
47
+ "missed-call"
152
48
  ],
153
- "license": "MIT",
154
49
  "homepage": "https://seldonframe.com",
155
50
  "repository": {
156
51
  "type": "git",