@seldonframe/mcp 1.0.8 → 1.1.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,7 +1,7 @@
1
1
  {
2
2
  "name": "@seldonframe/mcp",
3
- "version": "1.0.8",
4
- "description": "MCP server for SeldonFrame — AI-native Business OS platform. One command creates a real, hosted workspace with a website, booking page, intake form, CRM, and AI agents. v1.0.8: bulletproof finalize_workspace guardrail (create_workspace response now leads with a DO_NOT_DISPLAY warning string and hides URLs under _pending_after_email so models physically can't paste them to the operator without first calling finalize_workspace). New city/state args on create_workspace drive timezone inference (San Diego, CA America/Los_Angeles instead of UTC). Backend fixes shipped alongside: classifier broadened so 'Heating', 'Cooling', 'AC', 'Furnace' etc. resolve to local_service / HVAC personality (was leaking to coaching wrong pipeline stages); 'Your Business' nav placeholder eliminated (workspace.name now flows through); booking duration now defaults to 60 min for HVAC/coaching, 30 min for legal/dental, 45 for agency; black-square hero corner artifacts removed; 'Brain v2' / 'Compiling Soul' loading messages reworded for operators.",
3
+ "version": "1.1.0",
4
+ "description": "MCP server for SeldonFrame — AI-native Business OS platform. One command creates a real, hosted workspace with a website, booking page, intake form, CRM, and AI agents. v1.1.0: ATOMIC WORKSPACE CREATION. New `create_full_workspace` tool runs the entire 13-step pipeline server-side in one call workspace, business profile, CRM with industry-specific pipeline stages, booking page with availability, intake form, themed landing page, all deployed with live URLs. Same input always produces the same output, no Claude-Code orchestration, no retries, no 404s. Admin dashboard URL is now created ONLY by finalize_workspace (not by create_full_workspace or create_workspace) structural enforcement of the email-collection step instead of prompt-wording enforcement. The legacy create_workspace tool stays for backward compatibility but is no longer the recommended path.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "seldonframe-mcp": "src/index.js"
package/src/tools.js CHANGED
@@ -195,18 +195,154 @@ export const TOOLS = [
195
195
  },
196
196
  // URLs are in the payload (downstream consumers may rely on
197
197
  // them) but in a bucket whose name signals "do not display."
198
+ // May 2, 2026: admin_url REMOVED from this bucket. The admin
199
+ // browser URL is constructed only by finalize_workspace, so
200
+ // there is structurally nothing for Claude Code to leak before
201
+ // the email step. Public URLs stay (they're idempotent and
202
+ // useful to consumers other than the operator chat).
198
203
  _pending_after_email: {
199
204
  website_url: websiteUrl,
200
205
  booking_url: bookingUrl,
201
206
  intake_url: intakeUrl,
202
- admin_url: adminUrl,
203
- admin_url_expires_at: result.bearer_token_expires_at ?? null,
204
207
  public_urls: result.public_urls ?? publicUrls,
205
208
  },
206
209
  };
207
210
  return firstEver ? withFirstCallBanner(payload) : payload;
208
211
  },
209
212
  },
213
+ {
214
+ name: "create_full_workspace",
215
+ description:
216
+ "PREFERRED for new workspaces. Atomic, server-side workspace creation: takes structured business info and creates everything in ONE call — workspace, business profile, CRM with industry-specific pipeline stages, booking page with availability, intake form, themed landing page, all deployed with live URLs. " +
217
+ "Use this instead of create_workspace + a long sequence of customization tools. The pipeline runs server-side with a fixed order — same input always produces same output, no retries, no 404s. " +
218
+ "MANDATORY FOLLOW-UP: After this returns `status: 'ready'`, ask the operator verbatim 'What email should I use for your account? This is where you'll get your login link and notifications.' Then call `finalize_workspace({ workspace_id, email })`. The admin dashboard URL is ONLY created by finalize_workspace — it does not exist in this response (so there's nothing for you to display prematurely). " +
219
+ "Example: create_full_workspace({ business_name: 'Summit Air Comfort', city: 'Phoenix', state: 'AZ', phone: '(480) 555-2100', services: ['AC repair', 'heating installation', 'duct cleaning'], business_description: 'Residential and commercial HVAC in Phoenix', review_count: 950, review_rating: 4.7, trust_signals: ['licensed', 'bonded', 'insured'], emergency_service: true, same_day: true, service_area: ['Scottsdale', 'Tempe', 'Mesa'] })",
220
+ inputSchema: obj(
221
+ {
222
+ business_name: str("Business display name (e.g. 'Summit Air Comfort')."),
223
+ city: str("Operator's city. Drives timezone inference."),
224
+ state: str("US state code or full name (or Canadian province). Drives timezone inference."),
225
+ phone: str("Business phone, any format. Renders in nav, hero, footer."),
226
+ services: {
227
+ type: "array",
228
+ description:
229
+ "Services / offerings the business provides — each as a plain string. The classifier reads these to pick the right CRM personality (HVAC, legal, dental, coaching, agency, default).",
230
+ items: { type: "string" },
231
+ },
232
+ business_description: str(
233
+ "One paragraph describing the business — drives the hero subhead, about section, and (critically) the personality classifier. Include industry words verbatim ('residential HVAC', 'family-owned plumbing', 'dental practice')."
234
+ ),
235
+ review_count: { type: "number", description: "Optional — number of reviews. Surfaces in trust strip + hero proof metric." },
236
+ review_rating: { type: "number", description: "Optional — average star rating (e.g. 4.7). Surfaces in trust strip." },
237
+ certifications: {
238
+ type: "array",
239
+ description: "Optional — credentials like ['EPA-certified', 'NATE-certified']. Surfaces in trust strip.",
240
+ items: { type: "string" },
241
+ },
242
+ trust_signals: {
243
+ type: "array",
244
+ description: "Optional — short claims like ['licensed', 'bonded', 'insured']. Surfaces in trust strip.",
245
+ items: { type: "string" },
246
+ },
247
+ emergency_service: { type: "boolean", description: "Optional — operator offers 24/7 emergency service. Surfaces in nav + hero." },
248
+ same_day: { type: "boolean", description: "Optional — same-day service available. Surfaces in trust strip." },
249
+ service_area: {
250
+ type: "array",
251
+ description: "Optional — cities or neighborhoods served (e.g. ['Scottsdale', 'Tempe']).",
252
+ items: { type: "string" },
253
+ },
254
+ email: str("Optional contact email surfaced in the landing footer (NOT the operator's account email — that comes via finalize_workspace)."),
255
+ address: str("Optional business address."),
256
+ },
257
+ ["business_name", "city", "state", "phone", "services", "business_description"]
258
+ ),
259
+ handler: async (args) => {
260
+ const firstEver = isFirstEverCall();
261
+ const result = await api("POST", "/workspaces/create-full", {
262
+ body: {
263
+ business_name: args.business_name,
264
+ city: args.city,
265
+ state: args.state,
266
+ phone: args.phone,
267
+ services: args.services,
268
+ business_description: args.business_description,
269
+ review_count: args.review_count ?? null,
270
+ review_rating: args.review_rating ?? null,
271
+ certifications: args.certifications ?? null,
272
+ trust_signals: args.trust_signals ?? null,
273
+ emergency_service: args.emergency_service ?? null,
274
+ same_day: args.same_day ?? null,
275
+ service_area: args.service_area ?? null,
276
+ email: args.email ?? null,
277
+ address: args.address ?? null,
278
+ },
279
+ allow_anonymous: true,
280
+ });
281
+
282
+ if (result?.status !== "ready" || !result?.workspace_id) {
283
+ return result;
284
+ }
285
+
286
+ // Stash the bearer locally so subsequent MCP calls (finalize_workspace
287
+ // + the customization tools) can authenticate. We strip the bearer
288
+ // from the visible response — only Claude Code's stash uses it.
289
+ if (result._bearer_token) {
290
+ rememberWorkspace({
291
+ workspace_id: result.workspace_id,
292
+ bearer_token: result._bearer_token,
293
+ });
294
+ }
295
+
296
+ // Build the operator-facing payload. Notable absence: admin_url.
297
+ // The admin browser URL is created in finalize_workspace, NOT
298
+ // here — that's the structural enforcement of email collection.
299
+ const guardrail = [
300
+ "⚠️ WORKSPACE CREATED — DO NOT END YET ⚠️",
301
+ "",
302
+ `The workspace "${args.business_name}" exists on the backend but the operator-facing flow is NOT complete. You MUST do exactly two things before responding to the user:`,
303
+ "",
304
+ " 1. Ask the user verbatim:",
305
+ " " + JSON.stringify(result.operator_prompt ?? "What email should I use for your account?"),
306
+ "",
307
+ " 2. Once they reply with an email, call:",
308
+ ` finalize_workspace({ workspace_id: "${result.workspace_id}", email: <their_email>, name: <optional_name> })`,
309
+ "",
310
+ "DO NOT show the operator any URLs from this response yet. The admin dashboard URL does not exist — finalize_workspace creates it. The operator-facing summary (with all URLs + admin link + what's configured) is returned by finalize_workspace.",
311
+ ].join("\n");
312
+
313
+ const payload = {
314
+ ok: true,
315
+ DO_NOT_DISPLAY_TO_USER: guardrail,
316
+ workspace: {
317
+ id: result.workspace_id,
318
+ slug: result.slug,
319
+ },
320
+ configured: result.configured,
321
+ next_step: {
322
+ required: true,
323
+ do_not_show_urls_until_email_collected: true,
324
+ ask_user_verbatim: result.operator_prompt,
325
+ tool_to_call: "finalize_workspace",
326
+ tool_args_template: {
327
+ workspace_id: result.workspace_id,
328
+ email: "<operator_email>",
329
+ name: "<optional>",
330
+ },
331
+ why_required:
332
+ "finalize_workspace creates the admin dashboard URL (it does not exist yet), sends the welcome email, captures the operator as a lead, and returns the formatted operator-facing summary. Skipping it leaves the operator with no admin access at all.",
333
+ consequence_of_skipping:
334
+ "The admin dashboard URL is structurally not in this response. Skipping finalize_workspace means the operator can't get into their workspace. This IS a broken flow.",
335
+ },
336
+ // Internal-only — public URLs only. No admin URL anywhere.
337
+ _pending_after_email: {
338
+ website_url: result.public_urls?.home ?? null,
339
+ booking_url: result.public_urls?.book ?? null,
340
+ intake_url: result.public_urls?.intake ?? null,
341
+ },
342
+ };
343
+ return firstEver ? withFirstCallBanner(payload) : payload;
344
+ },
345
+ },
210
346
  {
211
347
  name: "list_workspaces",
212
348
  description: "List all workspaces known to this device (plus any Pro workspaces if SELDONFRAME_API_KEY is set).",
package/src/welcome.js CHANGED
@@ -4,7 +4,7 @@
4
4
  // no architecture lecture, no "Soul" / "Cal.diy" / "Formbricks" /
5
5
  // "Brain v2" jargon.
6
6
 
7
- export const VERSION = "1.0.8";
7
+ export const VERSION = "1.1.0";
8
8
 
9
9
  export const WELCOME_MARKDOWN = `# SeldonFrame — create a real Business OS in one conversation
10
10
 
@@ -50,29 +50,38 @@ welcome email, and no follow-up channel. Always finish with step 4.
50
50
  - Top 3-5 services / products
51
51
  - Brief description (1 sentence)
52
52
 
53
- 2. **Create the workspace.** Call \`create_workspace\` with the
54
- structured fields. ALWAYS include city + state when the user
55
- mentions a location (drives timezone), and ALWAYS include the
56
- industry words verbatim in business_description (drives CRM
57
- personality + pipeline stages):
53
+ 2. **Create the workspace atomically.** Call \`create_full_workspace\`
54
+ (the PREFERRED tool for new workspaces). One call the entire
55
+ workspace deploys server-side: org + business profile + CRM with
56
+ industry-specific pipeline + booking page with availability +
57
+ intake form + themed landing page. Same input always produces
58
+ the same output — no retries, no 404s, no Claude-Code orchestration.
58
59
  \`\`\`
59
- create_workspace({
60
- name: "Pacific Coast Heating & Air",
61
- phone: "(555) 123-4567",
60
+ create_full_workspace({
61
+ business_name: "Pacific Coast Heating & Air",
62
62
  city: "San Diego",
63
63
  state: "CA",
64
- business_description: "Family-owned residential HVAC contractor — heating, cooling, AC repair.",
65
- services: [
66
- { name: "AC Repair" },
67
- { name: "Heating Installation" },
68
- { name: "Indoor Air Quality" }
69
- ]
64
+ phone: "(555) 123-4567",
65
+ services: ["AC Repair", "Heating Installation", "Indoor Air Quality"],
66
+ business_description: "Family-owned residential HVAC contractor — heating, cooling, AC repair in the San Diego area.",
67
+ // Optional but recommended — enrich the hero proof metrics:
68
+ review_count: 950,
69
+ review_rating: 4.7,
70
+ trust_signals: ["licensed", "bonded", "insured"],
71
+ emergency_service: true,
72
+ same_day: true,
73
+ service_area: ["San Diego", "Chula Vista", "Oceanside"]
70
74
  })
71
75
  \`\`\`
72
- The response is a guardrail payload — its top field is a giant
73
- "DO NOT DISPLAY" warning string and the URLs live under
74
- \`_pending_after_email\`. Do NOT present anything from this
75
- response to the operator. Move directly to step 3.
76
+ The response carries a guardrail (\`DO_NOT_DISPLAY_TO_USER\`) and
77
+ intentionally OMITS the admin dashboard URL it does not exist
78
+ yet. Do NOT present anything from this response to the operator.
79
+ Move directly to step 3.
80
+
81
+ The legacy \`create_workspace\` tool still works for backward
82
+ compat but is fragile (assumes Claude Code orchestrates the
83
+ follow-up customization). Prefer \`create_full_workspace\` for
84
+ every new flow.
76
85
 
77
86
  3. **Ask the operator for their email — VERBATIM.** Use exactly this
78
87
  wording so the operator understands why it's needed:
@@ -82,21 +91,28 @@ welcome email, and no follow-up channel. Always finish with step 4.
82
91
  is required to keep their admin login alive past 7 days — then
83
92
  ask again. Don't move on without an answer.
84
93
 
85
- 4. **MANDATORY: call \`finalize_workspace({ email })\`.** This is the
86
- one tool call that closes the loop:
94
+ 4. **MANDATORY: call \`finalize_workspace({ workspace_id, email })\`.**
95
+ This is the one tool call that closes the loop AND creates the
96
+ admin dashboard URL (which doesn't exist until this runs):
87
97
  \`\`\`
88
- finalize_workspace({ email: "max@precisionplumbing.com", name: "Max" })
98
+ finalize_workspace({
99
+ workspace_id: "<from-create-full-response>",
100
+ email: "max@pacificcoasthvac.com",
101
+ name: "Max"
102
+ })
89
103
  \`\`\`
90
- It sends the welcome email (with all the URLs), captures the
91
- operator as a lead in our CRM, and returns a \`summary\` field
92
- with the formatted final output. PARAPHRASE that summary
93
- verbatim to the operator that's how they see what was
94
- configured (CRM personality, pipeline stages, live URLs,
95
- admin link, email confirmation).
104
+ It mints the admin auth token, sends the welcome email (with
105
+ all the URLs and the admin link), captures the operator as a
106
+ lead in our CRM, and returns a \`summary\` field with the
107
+ formatted final output. PARAPHRASE that summary verbatim to the
108
+ operator that's how they see what was configured (CRM
109
+ personality, pipeline stages, live URLs, admin link, email
110
+ confirmation).
96
111
 
97
112
  Alternative: call \`collect_operator_email({ email })\` if you
98
113
  want finer control without the formatted summary. Either tool
99
- satisfies step 4; skipping both does not.
114
+ satisfies step 4; skipping both does not — and the operator
115
+ is left with NO admin access at all.
100
116
 
101
117
  After step 4 the operator can customize their workspace through
102
118
  further natural-language requests ("change the headline to …",
@@ -107,14 +123,21 @@ further natural-language requests ("change the headline to …",
107
123
 
108
124
  ## What the tools do (operator language only)
109
125
 
110
- - **\`create_workspace\`** — creates the live business OS (website,
111
- booking, intake form, CRM, AI agents). Always the first call.
112
- - **\`finalize_workspace\`** MANDATORY closing call. Bundles email
113
- collection (welcome email + lead capture) and returns the
114
- formatted final summary Claude Code paraphrases verbatim to the
115
- operator. Always the last call of every workspace creation flow.
116
- - **\`collect_operator_email\`** same effect as finalize_workspace
117
- but without the formatted summary. Use either; never skip both.
126
+ - **\`create_full_workspace\`** — PREFERRED. Atomic, server-side
127
+ workspace creation. One call entire business OS deployed.
128
+ Always the first call for new workspaces.
129
+ - **\`create_workspace\`** legacy fallback (kept for backward
130
+ compat). Use create_full_workspace instead for new flows.
131
+ - **\`finalize_workspace\`** MANDATORY closing call. Mints the
132
+ admin auth token (the admin URL doesn't exist until this runs),
133
+ bundles email collection (welcome email + lead capture), and
134
+ returns the formatted final summary Claude Code paraphrases
135
+ verbatim to the operator. Always the last call of every
136
+ workspace creation flow.
137
+ - **\`collect_operator_email\`** — narrower variant of
138
+ finalize_workspace that only sends the welcome email + captures
139
+ the lead. Doesn't return the formatted summary. Use either;
140
+ never skip both.
118
141
  - **\`update_landing_content\`** / **\`update_landing_section\`** —
119
142
  edit the website's headline, subhead, sections, copy.
120
143
  - **\`update_theme\`** — change colors, fonts, dark/light mode.