@seldonframe/mcp 1.9.0 → 1.10.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.
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@seldonframe/mcp",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
+ "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).",
4
5
  "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).",
5
6
  "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.",
6
7
  "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.",
package/src/tools.js CHANGED
@@ -2667,6 +2667,126 @@ export const TOOLS = [
2667
2667
  },
2668
2668
  },
2669
2669
 
2670
+ // ─── v1.10.0 — block customization (regenerate / reorder / image) ──────
2671
+ //
2672
+ // These tools let the operator iterate on a workspace AFTER the v2
2673
+ // creation flow has shipped: regenerate one block with new
2674
+ // instructions ("make the hero punchier"), reorder landing sections
2675
+ // ("move FAQ to the bottom"), or upload an image (logo, hero
2676
+ // background).
2677
+ //
2678
+ // Thin harness, fat skill: server-side these tools do NO creative
2679
+ // work. regenerate_block just bundles the IDE agent everything it
2680
+ // needs (current props, workspace summary, brain patterns); the
2681
+ // agent's own LLM does the actual regeneration and calls
2682
+ // persist_block to save the result.
2683
+
2684
+ {
2685
+ name: "regenerate_block",
2686
+ description:
2687
+ "Get the bundle needed to regenerate ONE v2 page block with new operator instructions. Use this when the operator asks for a targeted change to an existing block ('make the hero punchier', 'add a card about kids cuts', 'rewrite the FAQ to be less salesy'). " +
2688
+ "Returns: current_props (so your LLM can iterate rather than start fresh), workspace_summary (business name, industry, services, voice from the workspace's soul), brain_patterns (anonymized cross-workspace patterns for this vertical), customization_history (previous edits — useful for understanding what NOT to revert), and the operator's new_instructions (echoed back so they're visible in your context). " +
2689
+ "The next move is YOURS: fetch the block's SKILL.md via get_block_skill, generate new props that satisfy the prop schema while applying new_instructions, then call persist_block with `customization: { prompt: <new_instructions> }` to record the change. " +
2690
+ "If the block has never been persisted (status=first_generation), this is a normal first-time generation path — same downstream flow, just no current_props to iterate from. " +
2691
+ "Antifragile design note: this tool only ASSEMBLES context. Your LLM does the creative work. As models improve, regeneration quality improves with zero MCP changes.",
2692
+ inputSchema: obj(
2693
+ {
2694
+ workspace_id: str("Workspace id from create_workspace_v2."),
2695
+ block_name: str(
2696
+ "Block name to regenerate. Must match a v2 block: hero, services, about, faq, cta, booking, intake.",
2697
+ ),
2698
+ new_instructions: str(
2699
+ "Optional: the operator's natural-language regeneration request ('make it more urgent', 'shorter copy', 'less salesy'). When provided, surfaced in the response and used in the customization field of the subsequent persist_block call. Omit when the operator just wants a fresh roll without specific guidance.",
2700
+ ),
2701
+ },
2702
+ ["workspace_id", "block_name"],
2703
+ ),
2704
+ handler: async (args) => {
2705
+ const ws = args.workspace_id;
2706
+ const blockName = encodeURIComponent(args.block_name);
2707
+ const qs = args.new_instructions
2708
+ ? `?new_instructions=${encodeURIComponent(args.new_instructions)}`
2709
+ : "";
2710
+ const result = await api(
2711
+ "GET",
2712
+ `/workspace/v2/blocks/${blockName}/regenerate${qs}`,
2713
+ { workspace_id: ws },
2714
+ );
2715
+ return result;
2716
+ },
2717
+ },
2718
+
2719
+ {
2720
+ name: "reorder_landing_sections",
2721
+ description:
2722
+ "Reorder the sections of a workspace's landing page WITHOUT changing their content. Use when the operator says 'move FAQ to the bottom', 'put services after the about section', 'rearrange so the CTA is below testimonials'. " +
2723
+ "Pass `new_order` as the full ordered array of section types as they should appear top-to-bottom. The multiset of types in new_order MUST equal the current landing's section types — no add/remove. Section types include: hero, services-grid, about, mid-cta, faq, testimonials, trust-strip, emergency-strip, service-area, partners, footer (the actual set depends on what's currently on the page). " +
2724
+ "Returns the new sections_order on success or validation_errors on failure (missing/extra types, duplicates). For content edits use update_landing_section. To regenerate a block's content use regenerate_block. To get the current order, fetch the workspace's landing or call regenerate_block (which exposes block names) — most landing pages start as: hero → services → about → faq → mid-cta.",
2725
+ inputSchema: obj(
2726
+ {
2727
+ workspace_id: str("Workspace id."),
2728
+ new_order: {
2729
+ type: "array",
2730
+ description:
2731
+ "Ordered array of section type strings. Must contain EVERY section type currently on the landing page, exactly once each. Example: [\"hero\", \"services-grid\", \"about\", \"mid-cta\", \"faq\"].",
2732
+ items: { type: "string" },
2733
+ },
2734
+ },
2735
+ ["workspace_id", "new_order"],
2736
+ ),
2737
+ handler: async (args) => {
2738
+ const ws = args.workspace_id;
2739
+ const result = await api("POST", "/workspace/v2/landing/reorder", {
2740
+ body: { workspace_id: ws, new_order: args.new_order },
2741
+ workspace_id: ws,
2742
+ });
2743
+ return result;
2744
+ },
2745
+ },
2746
+
2747
+ {
2748
+ name: "upload_workspace_image",
2749
+ description:
2750
+ "Upload an image to a workspace and apply it to one of two slots: 'logo' (replaces organizations.theme.logoUrl, surfaces in header / footer / og-image / favicon) or 'hero_background' (replaces the hero section's background image and re-renders the landing page). " +
2751
+ "Use when the operator says 'use this as my logo', 'replace the hero image with this photo', 'change the header logo'. " +
2752
+ "Pass the image bytes as base64 in `image_data_b64`. The MCP server forwards them to Vercel Blob; SSL + CDN are auto-provisioned. Max 5 MB; allowed types: image/png, image/jpeg, image/webp, image/svg+xml, image/gif. " +
2753
+ "Returns the public Blob URL on success; that URL is now live on the workspace's public surface within seconds. " +
2754
+ "If the operator gave you a local file path, read it as bytes and base64-encode it before calling this tool. If they gave you a URL, fetch it first then re-upload (we re-host so the asset is on our CDN, not theirs). " +
2755
+ "Antifragile design: server only validates file shape + applies URL to the right column. Your LLM picks which slot ('they said logo, that maps to slot=logo'). As you get better at intent-mapping, the harness doesn't change.",
2756
+ inputSchema: obj(
2757
+ {
2758
+ workspace_id: str("Workspace id."),
2759
+ slot: str(
2760
+ "Image slot: 'logo' (workspace logo, used in header/footer/og-image) or 'hero_background' (hero section background image). Other slots may be added in future versions.",
2761
+ ),
2762
+ file_name: str(
2763
+ "Original filename (for the blob path). Sanitized server-side — special chars stripped, path traversal blocked.",
2764
+ ),
2765
+ content_type: str(
2766
+ "MIME type. Must be one of: image/png, image/jpeg, image/webp, image/svg+xml, image/gif.",
2767
+ ),
2768
+ image_data_b64: str(
2769
+ "Image bytes, base64-encoded. Max 5 MB after decoding.",
2770
+ ),
2771
+ },
2772
+ ["workspace_id", "slot", "file_name", "content_type", "image_data_b64"],
2773
+ ),
2774
+ handler: async (args) => {
2775
+ const ws = args.workspace_id;
2776
+ const result = await api("POST", "/workspace/v2/images", {
2777
+ body: {
2778
+ workspace_id: ws,
2779
+ slot: args.slot,
2780
+ file_name: args.file_name,
2781
+ content_type: args.content_type,
2782
+ image_data_b64: args.image_data_b64,
2783
+ },
2784
+ workspace_id: ws,
2785
+ });
2786
+ return result;
2787
+ },
2788
+ },
2789
+
2670
2790
  // ─── v1.6.0 — brain layer (Karpathy LLM-Wiki) ───────────────────────────
2671
2791
  //
2672
2792
  // Two-layer brain stored as a file-tree of markdown notes:
package/src/welcome.js CHANGED
@@ -8,7 +8,7 @@
8
8
  // stripped. `create_full_workspace` is the only workspace-creation
9
9
  // path mentioned anywhere in this briefing.
10
10
 
11
- export const VERSION = "1.9.0";
11
+ export const VERSION = "1.10.0";
12
12
 
13
13
  export const WELCOME_MARKDOWN = `# SeldonFrame — create a real Business OS in one conversation
14
14
 
@@ -185,6 +185,19 @@ further natural-language requests ("change the headline to …",
185
185
  + renders + replaces the matching section in the workspace's landing.
186
186
  - **\`complete_workspace_v2\`** — marks the v2 flow finished, reports which
187
187
  blocks landed.
188
+ - **\`regenerate_block\`** (v1.10+) — bundles current props + workspace
189
+ summary + brain patterns + the operator's new instructions for
190
+ block re-generation ("make the hero punchier", "rewrite the FAQ to
191
+ be less salesy"). Server only assembles context; YOUR LLM does the
192
+ generation, then call persist_block with \`customization\`.
193
+ - **\`reorder_landing_sections\`** (v1.10+) — reorder landing-page
194
+ sections without changing content. Pass the full ordered array of
195
+ section types; multiset must equal current. For content edits use
196
+ update_landing_section; for regeneration use regenerate_block.
197
+ - **\`upload_workspace_image\`** (v1.10+) — upload a base64-encoded
198
+ image to slot=logo (organizations.theme.logoUrl) or
199
+ slot=hero_background (Blueprint.landing hero imageUrl + landing
200
+ re-render). 5 MB max, image/* MIME types. Vercel Blob auto-CDN.
188
201
  - **\`read_brain_path\`** / **\`list_brain_dir\`** — read the workspace's
189
202
  layer-1 brain (notes about THIS workspace's customers, voice, pipeline
190
203
  patterns). Use BEFORE generating blocks; reads tick the note's \`uses\`
@@ -244,4 +257,4 @@ admin dashboard. Pre-fills their email automatically.
244
257
  <https://seldonframe.com> · **Discord:** <https://discord.gg/sbVUu976NW>
245
258
  `;
246
259
 
247
- export const FIRST_CALL_BANNER = `🚀 SeldonFrame v1.4.1 is connected. PREFERRED workspace creation: create_workspace_v2 → IN PARALLEL for all 7 recommended_blocks (hero, services, about, faq, cta, booking, intake): get_block_skill + persist_block → complete_workspace_v2 → finalize_workspace({ workspace_id, email }). The v2 flow puts YOUR LLM in charge of every operator-facing surface using one SKILL.md per block. Each block's prop schema is server-validated. Run blocks in PARALLEL (Promise.all) — sequential takes 60+ seconds. Every URL is real. NEVER create local files. Skipping finalize_workspace leaves the operator with no admin login.`;
260
+ export const FIRST_CALL_BANNER = `🚀 SeldonFrame v1.10.0 is connected. PREFERRED workspace creation: create_workspace_v2 → IN PARALLEL for all 7 recommended_blocks (hero, services, about, faq, cta, booking, intake): get_block_skill + persist_block → complete_workspace_v2 → finalize_workspace({ workspace_id, email }). The v2 flow puts YOUR LLM in charge of every operator-facing surface using one SKILL.md per block. Each block's prop schema is server-validated. Run blocks in PARALLEL (Promise.all) — sequential takes 60+ seconds. v1.10+ TIER 2 CUSTOMIZE TOOLS: regenerate_block (re-do one block with operator instructions; thin-harness — server bundles context, your LLM generates), reorder_landing_sections (purely mechanical reorder by section type), upload_workspace_image (base64 image → Vercel Blob → applied to slot=logo or slot=hero_background). Every URL is real. NEVER create local files. Skipping finalize_workspace leaves the operator with no admin login.`;