@seldonframe/mcp 1.53.0 → 1.56.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,6 @@
1
1
  {
2
2
  "name": "@seldonframe/mcp",
3
- "version": "1.53.0",
3
+ "version": "1.56.0",
4
4
  "mcpName": "io.github.seldonframe/seldonframe-mcp",
5
5
  "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.",
6
6
  "license": "AGPL-3.0-or-later",
@@ -0,0 +1,157 @@
1
+ // v1.55.0 — Standalone builder for the finalize_workspace operator summary.
2
+ // Extracted from tools.js so we can unit-test the string-building logic
3
+ // in isolation (no MCP server boot, no HTTP shims, no snapshot fetcher).
4
+ //
5
+ // The handler in tools.js fetches the snapshot, computes duration, and
6
+ // passes the result here. This file just builds the string.
7
+
8
+ /**
9
+ * @param {object} args
10
+ * @param {object} args.snapshot — workspace snapshot from
11
+ * /api/v1/workspace/<id>/snapshot. Must include: workspace.name,
12
+ * public_urls.{home,book,intake}, chatbot (or null), ops_stack,
13
+ * available_automations, tier.
14
+ * @param {number} args.durationSec — total workspace creation time.
15
+ * @param {string|null} args.aestheticArchetype — workspace's classified
16
+ * archetype (from snapshot.theme.aestheticArchetype). Used in the
17
+ * closing landing-page nudge.
18
+ * @returns {string} the formatted operator summary
19
+ */
20
+ export function buildFinalizeSummary({ snapshot, durationSec, aestheticArchetype }) {
21
+ const ws = snapshot.workspace ?? {};
22
+ const businessName = ws.name ?? "Your workspace";
23
+ const chatbot = snapshot.chatbot ?? null;
24
+ const opsStack = snapshot.ops_stack ?? {};
25
+ const automations = snapshot.available_automations ?? [];
26
+ const tier = snapshot.tier ?? {};
27
+ const tierLabel = tier.current_tier_label ?? "Free";
28
+ const isPaid = tier.current_tier === "growth" || tier.current_tier === "scale";
29
+
30
+ // Extract client domain from the operator's input (the URL they scraped).
31
+ // We store it on workspace.settings.source_url upstream; fall back to
32
+ // the public preview URL host if unavailable.
33
+ const clientDomain =
34
+ (ws.settings && typeof ws.settings.source_url === "string"
35
+ ? new URL(ws.settings.source_url).host
36
+ : null) ?? "your client's site";
37
+
38
+ const lines = [];
39
+
40
+ // Header
41
+ lines.push(`✅ Client ops stack ready for ${businessName}. (${durationSec} seconds)`);
42
+ lines.push("");
43
+
44
+ // Chatbot embed snippet (the magic moment — paste on client's existing site)
45
+ if (chatbot && chatbot.embed_snippet) {
46
+ lines.push(`📞 AI receptionist — paste before </body> on ${clientDomain} to go live:`);
47
+ lines.push(chatbot.embed_snippet);
48
+ lines.push("");
49
+ lines.push(`🤖 Demo for your client: ${chatbot.preview_url ?? snapshot.public_urls?.home ?? ""}`);
50
+ lines.push(` (Chatbot live in TEST mode — share so your client can try it before pasting)`);
51
+ } else {
52
+ lines.push(`🤖 AI chatbot — scaffold pending. Retry:`);
53
+ lines.push(` create_agent({ archetype: "website-chatbot", channel: "web_chat" })`);
54
+ }
55
+ lines.push("");
56
+
57
+ // Ops stack URLs
58
+ lines.push(`📅 Booking: ${opsStack.booking_url ?? snapshot.public_urls?.book ?? ""}`);
59
+ lines.push(`📝 Intake: ${opsStack.intake_url ?? snapshot.public_urls?.intake ?? ""}`);
60
+ lines.push(`🔧 Admin: ${opsStack.admin_url ?? ""}`);
61
+ lines.push("");
62
+
63
+ // 7-automation callout
64
+ if (automations.length > 0) {
65
+ lines.push(`⚡ ${automations.length} more automations ready to deploy for this client:`);
66
+ const descriptions = {
67
+ "speed-to-lead": "text the lead within 30 sec of intake submission",
68
+ "missed-call-text-back": "auto-SMS when their phone goes unanswered",
69
+ "review-requester": "ask for a 5★ after every completed booking",
70
+ "appointment-confirm-sms": "reduce no-shows automatically",
71
+ "weather-aware-booking": "reschedule outdoor jobs when rain is forecast",
72
+ "daily-digest": "morning summary of yesterday's activity",
73
+ "win-back": "re-engage cancelled subscribers with a time-limited code",
74
+ };
75
+ for (const a of automations) {
76
+ const desc = descriptions[a.id] ?? "";
77
+ lines.push(` • ${a.name}${desc ? " — " + desc : ""}`);
78
+ }
79
+ lines.push(` Activate any: ${opsStack.automations_url ?? ""}`);
80
+ lines.push(
81
+ ` (Need API keys for SMS/email? Just ask — Claude will walk you through`,
82
+ );
83
+ lines.push(` Twilio / Resend / Stripe setup when an automation needs one.)`);
84
+ lines.push("");
85
+ }
86
+
87
+ // Tier + client portal
88
+ const tierUpsell = isPaid
89
+ ? "white-label + reseller pricing on Scale ($99/mo)"
90
+ : "Upgrade $9/mo for unlimited workspaces";
91
+ const clientPortalUrl = tier.client_portal_url ?? "";
92
+ lines.push(
93
+ `💼 Tier: ${tierLabel} · ${tierUpsell}` +
94
+ (clientPortalUrl ? ` · Client portal: ${clientPortalUrl}` : ""),
95
+ );
96
+ lines.push("");
97
+
98
+ // v1.55.x — derive appHost for the LLM-settings + client-portal links.
99
+ // SELDONFRAME_APP_BASE wins when set (white-label/staging); falls back
100
+ // to https://app.seldonframe.com. Trailing slash stripped to match the
101
+ // pattern used by v2/complete when building ops_stack.automations_url.
102
+ const appHost = (
103
+ process.env.SELDONFRAME_APP_BASE ?? "https://app.seldonframe.com"
104
+ ).replace(/\/$/, "");
105
+
106
+ // LLM key clarity — operators kept asking "which key is the chatbot
107
+ // using?" + had no recovery path when llm_credit_exhausted fired.
108
+ lines.push(
109
+ `🔑 Chatbot LLM key: uses your Claude Code key by default. Change at ${appHost}/settings/integrations/llm.`,
110
+ );
111
+ lines.push(
112
+ ` (If you see "llm_credit_exhausted", top up at console.anthropic.com/settings/billing.)`,
113
+ );
114
+ lines.push("");
115
+
116
+ // v1.55.x — Client portal demo callout. Closes the demo loop:
117
+ // chatbot (above) → booking → CRM → portal demo for the client.
118
+ // Slug is required; skip gracefully if absent (shouldn't happen in
119
+ // practice but the snapshot can theoretically lack it).
120
+ //
121
+ // The /demo URL is a one-click deep link: workspace creation seeds
122
+ // a "Demo Customer" contact + sample upcoming appointment + welcome
123
+ // message, and visiting /customer/<slug>/demo auto-establishes a
124
+ // portal session for that contact — no email, no magic link. The
125
+ // operator can paste this URL to a prospect and they land directly
126
+ // in a populated portal instead of a magic-link form with empty
127
+ // tabs. Demo data is filtered out of operator-facing CRM / pipeline
128
+ // / analytics by tag, so it stays isolated from real customer data.
129
+ const slug = ws.slug ?? "";
130
+ if (slug) {
131
+ const portalUrl = `${appHost}/customer/${slug}/demo`;
132
+ lines.push(`🎬 Demo the client portal (one-click, no login):`);
133
+ lines.push(` ${portalUrl}`);
134
+ lines.push(
135
+ ` Share this link with the prospect — opens directly as "Demo Customer" with a`,
136
+ );
137
+ lines.push(
138
+ ` sample appointment and welcome message. Demo data stays isolated from your`,
139
+ );
140
+ lines.push(` real CRM and pipelines.`);
141
+ lines.push(
142
+ ` Free tier shows the portal with SeldonFrame branding. Growth ($29/mo) unlocks custom domain + the agency's logo.`,
143
+ );
144
+ lines.push("");
145
+ }
146
+
147
+ // Landing-page nudge (closing)
148
+ const archetypeClause = aestheticArchetype ? ` in ${aestheticArchetype} style` : "";
149
+ lines.push(
150
+ `Want a landing page too? Just ask: "build a landing page for ${businessName}${archetypeClause}"`,
151
+ );
152
+ lines.push(
153
+ `— Claude will use the landing-page-creation skill to generate one${aestheticArchetype ? " with the archetype voice" : ""}.`,
154
+ );
155
+
156
+ return lines.join("\n");
157
+ }
package/src/tools.js CHANGED
@@ -20,6 +20,10 @@ import {
20
20
  // drafts assumed VERSION leaked in via client.js's transitive import;
21
21
  // it doesn't. Direct import = no runtime "VERSION is not defined".
22
22
  import { FIRST_CALL_BANNER, VERSION } from "./welcome.js";
23
+ // v1.55.0 — finalize_workspace summary builder extracted into its own
24
+ // module so it can be unit-tested in isolation. See
25
+ // packages/crm/tests/unit/finalize-summary-v1-55.spec.ts.
26
+ import { buildFinalizeSummary } from "./finalize-summary.js";
23
27
  // v1.10.1 — upload_workspace_image local_file_path branch reads the file
24
28
  // directly in the MCP-client process (which runs on the operator's
25
29
  // machine via the npm package). Reading + base64-encoding here means
@@ -840,8 +844,6 @@ export const TOOLS = [
840
844
  const wsName = snapshot?.workspace?.name ?? "Your workspace";
841
845
  const chatbot = snapshot?.chatbot ?? null;
842
846
  const tier = snapshot?.tier ?? null;
843
- const bookingInfo = snapshot?.booking ?? null;
844
- const intakeInfo = snapshot?.intake ?? null;
845
847
  if (!publicUrls.home || !publicUrls.book || !publicUrls.intake) {
846
848
  throw new Error(
847
849
  "Snapshot did not return public_urls (home/book/intake). Re-check the workspace.",
@@ -894,9 +896,8 @@ export const TOOLS = [
894
896
  }
895
897
 
896
898
  // Step 3: closing summary — formatted exactly the way Claude Code
897
- // should paraphrase to the operator. Pulls the personality label
898
- // + pipeline stages from the snapshot so the "What's configured"
899
- // section reflects the actual workspace shape.
899
+ // should paraphrase to the operator. Personality + pipeline are
900
+ // surfaced in the response payload (not the summary text).
900
901
  const personality =
901
902
  snapshot?.workspace?.settings?.crmPersonality ?? null;
902
903
  const personalityLabel =
@@ -905,125 +906,15 @@ export const TOOLS = [
905
906
  : null;
906
907
  const pipelineStages = personality?.pipeline?.stages ?? [];
907
908
 
908
- // 2026-05-15Agency-voice product moment summary. See spec
909
- // docs/superpowers/specs/2026-05-15-agency-output-product-moment-design.md
910
- const intakeSpecialNote =
911
- personality?.vertical === "hvac" || personality?.vertical === "plumbing"
912
- ? "emergency-line fallback"
913
- : "structured lead-qualification";
914
-
915
- const tierLabel = tier?.current_tier_label ?? "Free";
916
- const isPaid =
917
- tier?.current_tier === "growth" || tier?.current_tier === "scale";
918
- const isScale = tier?.current_tier === "scale";
919
-
920
- const intakeFieldCount = intakeInfo?.field_count ?? 0;
921
- const intakeTitle = intakeInfo?.title ?? "lead qualification";
922
-
923
- const lines = [];
924
-
925
- // Header
926
- lines.push(`✅ ${wsName} — client OS shipped.`);
927
- lines.push("");
928
- lines.push("Your client's stack is wired and live:");
929
- lines.push("");
930
-
931
- // Public site
932
- lines.push("🌐 Public site (paste a screenshot in your Slack)");
933
- lines.push(` ${publicUrls.home}`);
934
- lines.push("");
935
-
936
- // Chatbot
937
- if (chatbot && chatbot.embed_snippet) {
938
- lines.push("🤖 AI chatbot — paste on the client's existing site (before </body>):");
939
- lines.push(` ${chatbot.embed_snippet}`);
940
- lines.push(
941
- ` In ${String(chatbot.status).toUpperCase()} mode. Powered by your Claude Code key (swap in settings).`,
942
- );
943
- lines.push(
944
- ` Publish live: publish_agent({ agent_id: "${chatbot.agent_id}", status: "live" })`,
945
- );
946
- } else {
947
- lines.push("🤖 AI chatbot — scaffold pending. Retry:");
948
- lines.push(` create_agent({ archetype: "website-chatbot", channel: "web_chat" })`);
949
- }
950
- lines.push("");
951
-
952
- // Booking
953
- lines.push("📋 Booking page (client's customers self-serve appointments)");
954
- lines.push(` ${publicUrls.book}`);
955
- lines.push("");
956
-
957
- // Intake
958
- lines.push(`📝 Intake form (${intakeFieldCount}-question ${intakeTitle})`);
959
- lines.push(` ${publicUrls.intake}`);
960
- lines.push("");
961
-
962
- // Admin
963
- lines.push("🔐 Your admin (CRM, pipeline, leads, deals)");
964
- lines.push(` ${adminUrl}`);
965
- lines.push("");
966
-
967
- // Client portal
968
- if (tier?.client_portal_url) {
969
- lines.push("👥 Client portal (your client logs in here to see their leads + bookings)");
970
- lines.push(` ${tier.client_portal_url}`);
971
- if (isPaid) {
972
- lines.push(` ✅ Active. Forward this URL to your client; they log in via magic email.`);
973
- } else {
974
- lines.push(` 🔒 Growth tier ($29/mo) unlocks this for your client. Preview it`);
975
- lines.push(` yourself at the URL above right now.`);
976
- }
977
- lines.push("");
978
- }
979
-
980
- // What's wired
981
- lines.push("What's wired:");
982
- if (personalityLabel) {
983
- const stageCount = pipelineStages.length;
984
- lines.push(` • ${personalityLabel} personality • ${stageCount}-stage CRM pipeline`);
985
- }
986
- if (bookingInfo) {
987
- lines.push(
988
- ` • ${bookingInfo.hours_summary} bookings, ${bookingInfo.duration_minutes}-min slots`,
989
- );
990
- }
991
- if (intakeInfo) {
992
- lines.push(` • ${intakeFieldCount}-question intake with ${intakeSpecialNote}`);
993
- }
994
- if (chatbot) {
995
- lines.push(` • AI chatbot trained on the homepage (FAQ scaffold ready to refine)`);
996
- }
997
- lines.push(
998
- emailSent
999
- ? ` • Welcome email + admin link sent to ${a.email}`
1000
- : ` • Welcome email NOT yet sent (rerun finalize_workspace to retry)`,
1001
- );
1002
- lines.push("");
1003
-
1004
- // What you can prompt next (5 agency-meaningful examples)
1005
- lines.push("What you can prompt next:");
1006
- lines.push(` • "Refine the chatbot FAQ from the site" → update_website_chatbot`);
1007
- lines.push(` • "Add SMS missed-call-text-back automation" → install_archetype`);
1008
- lines.push(` • "Customize the hero with the client's brand voice" → customize_block`);
1009
- lines.push(` • "Wire Google Calendar so bookings sync" → connect_integration`);
1010
- lines.push(` • "Add a Spanish version of the landing page" → clone_workspace + translate`);
1011
- lines.push("");
1012
-
1013
- // Tier ladder (omit on Scale tier)
1014
- if (!isScale) {
1015
- lines.push(`Tier ladder (you're on ${tierLabel}):`);
1016
- lines.push(` Free → 1 client workspace, everything above wired`);
1017
- lines.push(` Growth $29/mo → 3 workspaces, client portal goes live, custom domain`);
1018
- lines.push(` (e.g. crm.youragency.com), SMS/email automations`);
1019
- lines.push(` Scale $99/mo → unlimited workspaces, full white-label, reseller pricing`);
1020
- lines.push("");
1021
- }
1022
-
1023
- // Closer
1024
- lines.push("Forward your client this admin link when ready. Or stay here and iterate.");
1025
-
1026
- const summary = lines.join("\n");
909
+ // v1.55.0Ops-stack-only workspace creation. The summary is
910
+ // built by buildFinalizeSummary in ./finalize-summary.js; we
911
+ // pass the snapshot + duration + archetype here.
912
+ const aestheticArchetype = snapshot?.theme?.aestheticArchetype ?? null;
913
+ const summary = buildFinalizeSummary({
914
+ snapshot,
915
+ durationSec: 0, // smoke test reports actual duration via Vercel logs, not the summary text
916
+ aestheticArchetype,
917
+ });
1027
918
 
1028
919
  return {
1029
920
  ok: emailSent || leadRecorded,
@@ -1050,37 +941,52 @@ export const TOOLS = [
1050
941
  lead_error: leadError,
1051
942
  personality: personalityLabel,
1052
943
  pipeline_stages: pipelineStages.map((s) => s?.name).filter(Boolean),
1053
- // 2026-05-15agency-meaningful next-step examples
944
+ // v1.55.0Ops-stack-first next-step ladder. Surfaces the
945
+ // chatbot-paste workflow + the 7 ready automations + the
946
+ // landing-page nudge so Claude Code can pick from a tight
947
+ // curated list rather than improvise tool calls.
1054
948
  next_steps_available: [
1055
949
  {
1056
- action: "publish_agent",
1057
- when: "operator has reviewed the chatbot and is ready to take it live for the client's website",
1058
- example: `publish_agent({ agent_id: "${chatbot?.agent_id ?? "ag_..."}", status: "live" })`,
950
+ id: "deploy_chatbot_embed",
951
+ label: "Paste chatbot embed on client's existing site",
952
+ action: "user_action",
953
+ payload: {
954
+ snippet: chatbot?.embed_snippet ?? null,
955
+ target: snapshot?.workspace?.settings?.source_url ?? null,
956
+ },
1059
957
  },
1060
958
  {
1061
- action: "update_website_chatbot",
1062
- when: "operator wants to refine the chatbot's FAQ before publishing",
1063
- example: `update_website_chatbot({ workspace_id, faq: [{ q: '...', a: '...' }] })`,
959
+ id: "promote_chatbot_to_live",
960
+ label: "Promote chatbot from TEST to LIVE (when ready for production)",
961
+ action: "publish_agent",
962
+ payload: { agent_id: chatbot?.agent_id ?? null },
1064
963
  },
1065
964
  {
1066
- action: "install_archetype",
1067
- when: "operator wants to wire pre-built automations (missed-call-text-back, speed-to-lead, review-requester)",
1068
- example: `install_archetype({ archetype: "missed-call-text-back" })`,
965
+ id: "activate_automation",
966
+ label: "Activate one of the 7 ready automations",
967
+ action: "open_dashboard",
968
+ payload: { url: snapshot?.ops_stack?.automations_url ?? null },
1069
969
  },
1070
970
  {
1071
- action: "customize_block",
1072
- when: "operator wants to refine landing-page hero / services / FAQ with brand voice",
1073
- example: `customize_block({ workspace_id, block_name: "hero", prompt: "make this feel more premium" })`,
971
+ id: "configure_integration",
972
+ label: "Configure Twilio / Resend / Stripe for SMS / email / payments",
973
+ action: "claude_assisted",
974
+ payload: { available_providers: ["twilio", "resend", "stripe"] },
1074
975
  },
1075
976
  {
1076
- action: "connect_integration",
1077
- when: "operator wants Google Calendar sync, Stripe payments, Twilio SMS",
1078
- example: `connect_integration({ workspace_id, provider: "google_calendar" })`,
977
+ id: "build_landing_page",
978
+ label: "Build a landing page (uses landing-page-creation skill)",
979
+ action: "claude_assisted",
980
+ payload: {
981
+ skill: "landing-page-creation",
982
+ archetype: snapshot?.theme?.aestheticArchetype ?? null,
983
+ },
1079
984
  },
1080
985
  {
1081
- action: "configure_llm_provider",
1082
- when: "operator wants to swap from the default Claude Code key to a different Anthropic key",
1083
- example: `configure_llm_provider({ workspace_id, provider: "anthropic", api_key: "sk-ant-..." })`,
986
+ id: "customize_chatbot_faq",
987
+ label: "Refine the chatbot's FAQ from source site content",
988
+ action: "claude_assisted",
989
+ payload: { agent_id: chatbot?.agent_id ?? null },
1084
990
  },
1085
991
  ],
1086
992
  };