@seldonframe/mcp 1.7.3 → 1.8.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.7.3",
3
+ "version": "1.8.0",
4
+ "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.",
4
5
  "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).",
5
6
  "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.",
6
7
  "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.",
package/src/tools.js CHANGED
@@ -2907,6 +2907,102 @@ export const TOOLS = [
2907
2907
  },
2908
2908
  },
2909
2909
 
2910
+ // ─── v1.8.0 — custom domains (paying tiers) ─────────────────────────────
2911
+ //
2912
+ // Operators on Growth ($29) or Scale ($99) can route their own
2913
+ // hostnames (joescuts.com, ironandoak.ca, etc.) to their workspace.
2914
+ // Free-tier workspaces stay on <slug>.app.seldonframe.com.
2915
+ //
2916
+ // Flow:
2917
+ // 1. add_custom_domain → registers with Vercel, returns DNS record
2918
+ // to add at the operator's registrar.
2919
+ // 2. operator adds the CNAME / A record at Cloudflare / Namecheap /
2920
+ // etc.
2921
+ // 3. verify_domain → polls Vercel, returns verified once DNS resolves.
2922
+ // Vercel auto-provisions SSL via Let's Encrypt.
2923
+ // 4. Subsequent traffic routes to the workspace automatically (proxy
2924
+ // checks workspace_domains FIRST before subdomain extraction).
2925
+
2926
+ {
2927
+ name: "add_custom_domain",
2928
+ description:
2929
+ "Add a custom hostname (e.g. 'joescuts.com', 'www.joescuts.com', 'bookings.joescuts.com') to the workspace. PAID FEATURE — requires Growth ($29/mo) or Scale ($99/mo); returns 402 upgrade_required on free tier. Returns DNS instructions the operator needs to add at their registrar (Cloudflare, Namecheap, GoDaddy, etc.). Once DNS propagates (typically 5min - 24h), call verify_domain to mark verified + enable routing. Vercel auto-provisions SSL once DNS resolves.",
2930
+ inputSchema: obj(
2931
+ {
2932
+ workspace_id: str("Workspace id."),
2933
+ hostname: str(
2934
+ "Hostname to register, lowercased and without scheme. Examples: 'joescuts.com', 'www.joescuts.com', 'shop.joescuts.com'.",
2935
+ ),
2936
+ },
2937
+ ["workspace_id", "hostname"],
2938
+ ),
2939
+ handler: async (args) => {
2940
+ const ws = args.workspace_id;
2941
+ const result = await api("POST", "/domains", {
2942
+ body: { op: "add", hostname: args.hostname },
2943
+ workspace_id: ws,
2944
+ });
2945
+ return result;
2946
+ },
2947
+ },
2948
+
2949
+ {
2950
+ name: "verify_domain",
2951
+ description:
2952
+ "Re-check DNS for a previously-added custom domain. Returns { verified: true } once Vercel sees the correct DNS record AND issues SSL — usually 5 minutes after the operator adds the CNAME / A record at their registrar, sometimes up to 24 hours depending on TTL. Returns { verified: false, recommended_records } when DNS still hasn't propagated; surface those recommendations to the operator so they can fix their registrar config.",
2953
+ inputSchema: obj(
2954
+ {
2955
+ workspace_id: str("Workspace id."),
2956
+ hostname: str("The hostname to re-verify (must already be added)."),
2957
+ },
2958
+ ["workspace_id", "hostname"],
2959
+ ),
2960
+ handler: async (args) => {
2961
+ const ws = args.workspace_id;
2962
+ const result = await api("POST", "/domains", {
2963
+ body: { op: "verify", hostname: args.hostname },
2964
+ workspace_id: ws,
2965
+ });
2966
+ return result;
2967
+ },
2968
+ },
2969
+
2970
+ {
2971
+ name: "list_workspace_domains",
2972
+ description:
2973
+ "List all custom domains registered to the workspace. Returns hostname, status (pending / verified / failed), DNS verification record, and primary flag for each. Allowed on all tiers — free workspaces will see an empty list since custom domains require a paid tier.",
2974
+ inputSchema: obj({ workspace_id: str("Workspace id.") }, ["workspace_id"]),
2975
+ handler: async (args) => {
2976
+ const ws = args.workspace_id;
2977
+ const result = await api("POST", "/domains", {
2978
+ body: { op: "list" },
2979
+ workspace_id: ws,
2980
+ });
2981
+ return result;
2982
+ },
2983
+ },
2984
+
2985
+ {
2986
+ name: "remove_workspace_domain",
2987
+ description:
2988
+ "Remove a custom domain from the workspace. Routes immediately stop responding for the removed hostname; SSL cert is preserved on Vercel for 30 days in case the operator wants to re-add it. Idempotent — no error if the domain was already removed.",
2989
+ inputSchema: obj(
2990
+ {
2991
+ workspace_id: str("Workspace id."),
2992
+ hostname: str("Hostname to remove."),
2993
+ },
2994
+ ["workspace_id", "hostname"],
2995
+ ),
2996
+ handler: async (args) => {
2997
+ const ws = args.workspace_id;
2998
+ const result = await api("POST", "/domains", {
2999
+ body: { op: "remove", hostname: args.hostname },
3000
+ workspace_id: ws,
3001
+ });
3002
+ return result;
3003
+ },
3004
+ },
3005
+
2910
3006
  {
2911
3007
  name: "list_brain_patterns",
2912
3008
  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.7.3";
11
+ export const VERSION = "1.8.0";
12
12
 
13
13
  export const WELCOME_MARKDOWN = `# SeldonFrame — create a real Business OS in one conversation
14
14
 
@@ -173,6 +173,11 @@ further natural-language requests ("change the headline to …",
173
173
  workspace via magic-link email. Use when the operator already has a
174
174
  workspace (created from another device) and wants to admin it from
175
175
  this IDE. Sends a confirmation email; tool polls until approved.
176
+ - **\`add_custom_domain\`** / **\`verify_domain\`** /
177
+ **\`list_workspace_domains\`** / **\`remove_workspace_domain\`** (v1.8+)
178
+ — register the operator's own hostname against the workspace.
179
+ PAID FEATURE on Growth ($29/mo) or Scale ($99/mo); free tier returns
180
+ 402 with upgrade CTA. Vercel auto-provisions SSL once DNS resolves.
176
181
  - **\`list_blocks\`** — lists v2 page-block primitives available.
177
182
  - **\`get_block_skill\`** — fetches one block's SKILL.md (the generation
178
183
  prompt + prop schema you read before generating props).