@seldonframe/mcp 1.17.0 → 1.18.1

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,8 @@
1
1
  {
2
2
  "name": "@seldonframe/mcp",
3
- "version": "1.17.0",
3
+ "version": "1.18.1",
4
+ "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.",
5
+ "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.",
4
6
  "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.",
5
7
  "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.",
6
8
  "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.",
package/src/tools.js CHANGED
@@ -3459,6 +3459,65 @@ export const TOOLS = [
3459
3459
  },
3460
3460
  },
3461
3461
 
3462
+ // ─── v1.18.0 — partner-agency sender domain (Resend) ────────────────────
3463
+
3464
+ {
3465
+ name: "register_partner_agency_sender_domain",
3466
+ description:
3467
+ "Register a sender domain for a partner agency so the agency can send transactional emails (welcome, magic-link, portal-access-code) FROM their own domain instead of welcome@seldonframe.com. " +
3468
+ "The SeldonFrame backend creates the domain in Resend (under our SF Resend account), and returns the DNS records (SPF, DKIM, MX) the agency must add at THEIR registrar. The agency does NOT need their own Resend account. " +
3469
+ "The default sender_local_part is 'welcome' — final sender becomes welcome@<domain>. Override with sender_local_part='hello' to get hello@<domain>. " +
3470
+ "After this call: the agency adds the DNS records, waits 5-60 min for propagation, then calls verify_partner_agency_sender_domain. Once Resend confirms verification, the agency's clients' transactional emails switch to the agency's sender automatically.",
3471
+ inputSchema: obj(
3472
+ {
3473
+ workspace_id: str("Workspace id (the bearer's workspace; used to resolve owning user)."),
3474
+ agency_id: str("Agency id from register_partner_agency."),
3475
+ domain: str("Domain to send from (e.g. 'acmeai.com'). Without scheme. The agency must control DNS for this domain."),
3476
+ sender_local_part: str(
3477
+ "Optional local-part of the sender address (default: 'welcome'). Final sender becomes <local>@<domain>.",
3478
+ ),
3479
+ },
3480
+ ["workspace_id", "agency_id", "domain"],
3481
+ ),
3482
+ handler: async (args) => {
3483
+ const ws = args.workspace_id;
3484
+ const result = await api("POST", "/partner-agencies", {
3485
+ body: {
3486
+ op: "register_sender_domain",
3487
+ agency_id: args.agency_id,
3488
+ domain: args.domain,
3489
+ sender_local_part: args.sender_local_part,
3490
+ },
3491
+ workspace_id: ws,
3492
+ });
3493
+ return result;
3494
+ },
3495
+ },
3496
+
3497
+ {
3498
+ name: "verify_partner_agency_sender_domain",
3499
+ description:
3500
+ "Trigger Resend's DNS verification for a partner agency's sender domain. Call this AFTER the agency has added the SPF/DKIM/MX records at their registrar. Returns the current verification status. When status flips to 'verified', the agency's verified_sender_at timestamp is set and chrome substitution kicks in for outbound emails on attached workspaces. Idempotent — safe to call repeatedly while DNS is propagating.",
3501
+ inputSchema: obj(
3502
+ {
3503
+ workspace_id: str("Workspace id (the bearer's workspace)."),
3504
+ agency_id: str("Agency id from register_partner_agency."),
3505
+ },
3506
+ ["workspace_id", "agency_id"],
3507
+ ),
3508
+ handler: async (args) => {
3509
+ const ws = args.workspace_id;
3510
+ const result = await api("POST", "/partner-agencies", {
3511
+ body: {
3512
+ op: "verify_sender_domain",
3513
+ agency_id: args.agency_id,
3514
+ },
3515
+ workspace_id: ws,
3516
+ });
3517
+ return result;
3518
+ },
3519
+ },
3520
+
3462
3521
  {
3463
3522
  name: "reorder_landing_sections",
3464
3523
  description:
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.17.0";
11
+ export const VERSION = "1.18.1";
12
12
 
13
13
  export const WELCOME_MARKDOWN = `# SeldonFrame — create a real Business OS in one conversation
14
14
 
@@ -306,4 +306,4 @@ admin dashboard. Pre-fills their email automatically.
306
306
  <https://seldonframe.com> · **Discord:** <https://discord.gg/sbVUu976NW>
307
307
  `;
308
308
 
309
- export const FIRST_CALL_BANNER = `🚀 SeldonFrame v1.17.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), upload_workspace_image (set logo/hero_background; v1.10.1+ accepts image_url or local_file_path — DON'T base64 unless you have to, the encoded string eats your tool-call token budget). v1.11+ STRUCTURAL PRIMITIVES: get_landing_structure → move_section → delete_section. INDEX-based, handle duplicate section types, atomic. v1.12+ COMPOSITE TREES: add_composite_section / update_composite_section — manifest ANY block (comparison, pricing, "how it works," stats, custom CTAs) from 12 low-level primitives. Server validates + renders; YOUR LLM composes. Read the SKILL.md via get_block_skill('composite') before composing. Every URL is real. NEVER create local files. Skipping finalize_workspace leaves the operator with no admin login.`;
309
+ export const FIRST_CALL_BANNER = `🚀 SeldonFrame v1.18.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. v1.10+ TIER 2 CUSTOMIZE TOOLS: regenerate_block (re-do one block with operator instructions; thin-harness — server bundles context, your LLM generates), upload_workspace_image (set logo/hero_background; v1.10.1+ accepts image_url or local_file_path — DON'T base64 unless you have to, the encoded string eats your tool-call token budget). v1.11+ STRUCTURAL PRIMITIVES: get_landing_structure → move_section → delete_section. INDEX-based, handle duplicate section types, atomic. v1.12+ COMPOSITE TREES: add_composite_section / update_composite_section — manifest ANY block (comparison, pricing, "how it works," stats, custom CTAs) from 12 low-level primitives. Server validates + renders; YOUR LLM composes. Read the SKILL.md via get_block_skill('composite') before composing. Every URL is real. NEVER create local files. Skipping finalize_workspace leaves the operator with no admin login.`;