@seldonframe/mcp 1.0.4 → 1.0.6
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 +2 -2
- package/src/client.js +12 -0
- package/src/tools.js +272 -27
- package/src/welcome.js +130 -132
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seldonframe/mcp",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "MCP server for SeldonFrame — AI-native Business OS platform. One command creates a real hosted workspace with
|
|
3
|
+
"version": "1.0.6",
|
|
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.6: NEVER-CREATE-LOCAL-FILES rule baked into the server's system instructions and into the create_workspace tool description so Claude Code reliably uses the MCP tool even when running inside a SeldonFrame source repo; rewritten create_workspace response payload with a copy-paste-ready operator summary (live URLs + email prompt) and zero internal jargon (no Soul / Cal.diy / Formbricks / Brain v2 references in operator-facing copy); welcome.md fully rewritten in operator language.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"seldonframe-mcp": "src/index.js"
|
package/src/client.js
CHANGED
|
@@ -139,6 +139,18 @@ export async function api(method, path, opts = {}) {
|
|
|
139
139
|
};
|
|
140
140
|
if (auth) headers.Authorization = auth;
|
|
141
141
|
|
|
142
|
+
// May 1, 2026 — inject x-org-id from the resolved workspace for
|
|
143
|
+
// EVERY tool call. Several CRM endpoints (contacts, deals,
|
|
144
|
+
// bookings, activities) use guardApiRequest which strictly
|
|
145
|
+
// requires x-org-id. Without this header, the bearer token alone
|
|
146
|
+
// returns 400 "Missing x-org-id" even though the bearer is
|
|
147
|
+
// workspace-scoped. Tools can opt out by passing
|
|
148
|
+
// `extra_headers: { "x-org-id": null }` if a route needs the
|
|
149
|
+
// header omitted, but the default is to send it.
|
|
150
|
+
if (workspace_id && !("x-org-id" in headers) && !("X-Org-ID" in headers)) {
|
|
151
|
+
headers["x-org-id"] = workspace_id;
|
|
152
|
+
}
|
|
153
|
+
|
|
142
154
|
const res = await fetch(`${API_BASE}${path}`, {
|
|
143
155
|
method,
|
|
144
156
|
headers,
|
package/src/tools.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
api,
|
|
3
|
+
API_INFO,
|
|
3
4
|
fetchText,
|
|
4
5
|
forgetWorkspace,
|
|
5
6
|
htmlToText,
|
|
@@ -41,18 +42,64 @@ export const TOOLS = [
|
|
|
41
42
|
{
|
|
42
43
|
name: "create_workspace",
|
|
43
44
|
description:
|
|
44
|
-
"Create a
|
|
45
|
+
"Create a LIVE, hosted SeldonFrame workspace with a public website, booking page, intake form, CRM, and AI agents — all on a real <slug>.app.seldonframe.com subdomain. Returns live URLs that work in any browser within seconds. " +
|
|
46
|
+
"THIS IS THE ONLY WAY to create a SeldonFrame workspace. Do NOT create local files (no soul.json, no scaffolded directory), do NOT call loadSoulPackage, do NOT modify the working directory — even if Claude Code is running inside the SeldonFrame source repo. Every workspace is a hosted production deployment. " +
|
|
47
|
+
"The first workspace is free and requires no API key. When the user mentions a phone, email, address, list of services, or testimonials, pass them as structured fields so the landing page renders with their real content immediately. " +
|
|
48
|
+
"Example: create_workspace({ name: 'Precision Plumbing Co', phone: '(555) 123-4567', business_description: 'Family-owned residential plumbing in Austin.', services: [{ name: 'Drain Cleaning' }, { name: 'Water Heater Repair' }, { name: 'Leak Detection' }] })",
|
|
45
49
|
inputSchema: obj(
|
|
46
50
|
{
|
|
47
51
|
name: str("Human-readable workspace name."),
|
|
48
|
-
source: str("Optional URL or description to seed the
|
|
52
|
+
source: str("Optional URL or description to seed the business profile from."),
|
|
53
|
+
phone: str("Operator's business phone (any format — we render as-is). Renders in nav, hero, and footer when set."),
|
|
54
|
+
email: str("Optional contact email surfaced in the landing footer."),
|
|
55
|
+
address: str("Optional business address. Comma-separated street, city, region, postal, country renders as-is."),
|
|
56
|
+
tagline: str("Short hero tagline (one line)."),
|
|
57
|
+
business_description: str("One paragraph about the business (used for hero subhead + about section)."),
|
|
58
|
+
services: {
|
|
59
|
+
type: "array",
|
|
60
|
+
description:
|
|
61
|
+
"List of services / products / offerings the business provides. Renders as the services grid on the landing page. Each item: { name, description? }.",
|
|
62
|
+
items: {
|
|
63
|
+
type: "object",
|
|
64
|
+
properties: {
|
|
65
|
+
name: { type: "string" },
|
|
66
|
+
description: { type: "string" },
|
|
67
|
+
},
|
|
68
|
+
required: ["name"],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
testimonials: {
|
|
72
|
+
type: "array",
|
|
73
|
+
description:
|
|
74
|
+
"Optional customer testimonials to seed the landing page's testimonials section. Each item: { quote, name?, role?, company? }.",
|
|
75
|
+
items: {
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: {
|
|
78
|
+
quote: { type: "string" },
|
|
79
|
+
name: { type: "string" },
|
|
80
|
+
role: { type: "string" },
|
|
81
|
+
company: { type: "string" },
|
|
82
|
+
},
|
|
83
|
+
required: ["quote"],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
49
86
|
},
|
|
50
87
|
["name"],
|
|
51
88
|
),
|
|
52
89
|
handler: async (args) => {
|
|
53
90
|
const firstEver = isFirstEverCall();
|
|
54
91
|
const result = await api("POST", "/workspace/create", {
|
|
55
|
-
body: {
|
|
92
|
+
body: {
|
|
93
|
+
name: args.name,
|
|
94
|
+
source: args.source ?? null,
|
|
95
|
+
phone: args.phone ?? null,
|
|
96
|
+
email: args.email ?? null,
|
|
97
|
+
address: args.address ?? null,
|
|
98
|
+
tagline: args.tagline ?? null,
|
|
99
|
+
business_description: args.business_description ?? null,
|
|
100
|
+
services: args.services ?? null,
|
|
101
|
+
testimonials: args.testimonials ?? null,
|
|
102
|
+
},
|
|
56
103
|
allow_anonymous: true,
|
|
57
104
|
});
|
|
58
105
|
const ws = result.workspace ?? result;
|
|
@@ -77,8 +124,34 @@ export const TOOLS = [
|
|
|
77
124
|
Object.entries(rawUrls).filter(([key]) => !key.startsWith("admin_")),
|
|
78
125
|
)
|
|
79
126
|
: null;
|
|
127
|
+
// May 2, 2026 — operator-facing response payload.
|
|
128
|
+
//
|
|
129
|
+
// Goals: lead with the live URLs (operators care about those
|
|
130
|
+
// first), prompt for the email next (the keystone of the
|
|
131
|
+
// onboarding loop), and never surface internal jargon
|
|
132
|
+
// (no "Soul" / "Cal.diy" / "Formbricks" / "Brain v2" /
|
|
133
|
+
// "loadSoulPackage" / etc).
|
|
134
|
+
//
|
|
135
|
+
// The `summary` string is what Claude Code should paraphrase to
|
|
136
|
+
// the operator verbatim — it's pre-formatted with bullets so
|
|
137
|
+
// copy-paste lands cleanly.
|
|
138
|
+
const websiteUrl = publicUrls?.home ?? null;
|
|
139
|
+
const bookingUrl = publicUrls?.book ?? null;
|
|
140
|
+
const intakeUrl = publicUrls?.intake ?? null;
|
|
141
|
+
const summaryLines = [`${ws.name} is live!`, ""];
|
|
142
|
+
if (websiteUrl) summaryLines.push(`- Website: ${websiteUrl}`);
|
|
143
|
+
if (bookingUrl) summaryLines.push(`- Book a call: ${bookingUrl}`);
|
|
144
|
+
if (intakeUrl) summaryLines.push(`- Service request form: ${intakeUrl}`);
|
|
145
|
+
if (adminUrl) summaryLines.push(`- Admin dashboard: ${adminUrl}`);
|
|
146
|
+
summaryLines.push("");
|
|
147
|
+
summaryLines.push(
|
|
148
|
+
"What's your email? I'll send you all these links and set up your admin login."
|
|
149
|
+
);
|
|
150
|
+
const summary = summaryLines.join("\n");
|
|
151
|
+
|
|
80
152
|
const payload = {
|
|
81
153
|
ok: true,
|
|
154
|
+
summary,
|
|
82
155
|
workspace: {
|
|
83
156
|
id,
|
|
84
157
|
name: ws.name,
|
|
@@ -86,23 +159,23 @@ export const TOOLS = [
|
|
|
86
159
|
tier: ws.tier ?? "free",
|
|
87
160
|
created_at: ws.created_at,
|
|
88
161
|
},
|
|
89
|
-
//
|
|
162
|
+
// Live URLs — present these to the operator first. Public
|
|
163
|
+
// ones first, admin URL second.
|
|
164
|
+
website_url: websiteUrl,
|
|
165
|
+
booking_url: bookingUrl,
|
|
166
|
+
intake_url: intakeUrl,
|
|
90
167
|
admin_url: adminUrl,
|
|
91
168
|
admin_url_expires_at: result.bearer_token_expires_at ?? null,
|
|
92
|
-
admin_url_message: adminUrl
|
|
93
|
-
? `⚡ Admin Dashboard (bookmark this!): ${adminUrl}\nClick to open the dashboard — no signup needed. Token expires in 7 days; re-mint with list_workspaces({}) when it does.`
|
|
94
|
-
: null,
|
|
95
|
-
urls: publicUrls,
|
|
96
169
|
public_urls: result.public_urls ?? publicUrls,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
170
|
+
// Email collection is the next required step. Claude Code
|
|
171
|
+
// should ask the operator for their email and then call
|
|
172
|
+
// collect_operator_email — DO NOT skip this.
|
|
173
|
+
next_step: {
|
|
174
|
+
ask_user:
|
|
175
|
+
"What's your email? I'll send you all these links and set up your admin login.",
|
|
176
|
+
tool_to_call: "collect_operator_email",
|
|
177
|
+
tool_args_template: { email: "<operator_email>", name: "<optional>" },
|
|
178
|
+
},
|
|
106
179
|
};
|
|
107
180
|
return firstEver ? withFirstCallBanner(payload) : payload;
|
|
108
181
|
},
|
|
@@ -207,6 +280,178 @@ export const TOOLS = [
|
|
|
207
280
|
};
|
|
208
281
|
},
|
|
209
282
|
},
|
|
283
|
+
{
|
|
284
|
+
name: "send_welcome_email",
|
|
285
|
+
description:
|
|
286
|
+
"Email the active workspace's four key URLs (landing, booking, intake, admin dashboard) to a user. Use this AFTER create_workspace, only when the user has explicitly given their email — never auto-send. The admin URL is bearer-token-scoped and expires in 7 days. Example: send_welcome_email({ email: 'alice@example.com', name: 'Alice' }).",
|
|
287
|
+
inputSchema: obj(
|
|
288
|
+
{
|
|
289
|
+
email: str("Recipient email address."),
|
|
290
|
+
name: str("Optional recipient name (used in the greeting)."),
|
|
291
|
+
workspace_id: str("Optional workspace override. Defaults to active workspace."),
|
|
292
|
+
},
|
|
293
|
+
["email"],
|
|
294
|
+
),
|
|
295
|
+
handler: async (a) => {
|
|
296
|
+
const workspaceId = a.workspace_id ?? getDefaultWorkspace();
|
|
297
|
+
if (!workspaceId) {
|
|
298
|
+
throw new Error(
|
|
299
|
+
"No workspace selected. Run create_workspace({ name: '…' }) first, or pass workspace_id.",
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
const bearer = getWorkspaceBearer(workspaceId);
|
|
303
|
+
if (!bearer) {
|
|
304
|
+
throw new Error(
|
|
305
|
+
`No local bearer token for workspace ${workspaceId}. This device did not create it. Re-run create_workspace or switch to the device that did.`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const snapshot = await api(
|
|
310
|
+
"GET",
|
|
311
|
+
`/workspace/${encodeURIComponent(workspaceId)}/snapshot`,
|
|
312
|
+
{ workspace_id: workspaceId },
|
|
313
|
+
);
|
|
314
|
+
const publicUrls = snapshot?.public_urls ?? {};
|
|
315
|
+
if (!publicUrls.home || !publicUrls.book || !publicUrls.intake) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
"Snapshot did not return public_urls (home/book/intake). Re-check the workspace.",
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Construct the bearer-scoped admin URL from the API base.
|
|
322
|
+
// API_INFO.base is `<host>/api/v1` — strip that suffix to get the app host.
|
|
323
|
+
const appHost = API_INFO.base.replace(/\/api\/v1\/?$/, "");
|
|
324
|
+
const adminUrl = `${appHost}/admin/${encodeURIComponent(workspaceId)}?token=${encodeURIComponent(bearer)}`;
|
|
325
|
+
|
|
326
|
+
await api("POST", "/email/send-welcome", {
|
|
327
|
+
body: {
|
|
328
|
+
email: a.email,
|
|
329
|
+
name: a.name ?? null,
|
|
330
|
+
workspace: {
|
|
331
|
+
landing_url: publicUrls.home,
|
|
332
|
+
booking_url: publicUrls.book,
|
|
333
|
+
intake_url: publicUrls.intake,
|
|
334
|
+
admin_url: adminUrl,
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
workspace_id: workspaceId,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
ok: true,
|
|
342
|
+
message: `Welcome email sent to ${a.email}`,
|
|
343
|
+
};
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: "collect_operator_email",
|
|
348
|
+
description:
|
|
349
|
+
"Onboarding-loop helper: ask the operator for their email post-create_workspace, then send the welcome email + record them as a lead in SeldonFrame's own CRM. Combines send_welcome_email + a lead-capture call so a single tool call closes the loop. Call this BEFORE further customization (configure_booking / customize_intake_form / install_vertical_pack) so the operator gets a permanent inbox record of their links. Example: collect_operator_email({ email: 'max@desertcool.com', name: 'Max' })",
|
|
350
|
+
inputSchema: obj(
|
|
351
|
+
{
|
|
352
|
+
email: str("Operator email — used as the welcome email recipient AND as the unique key for the SeldonFrame CRM lead."),
|
|
353
|
+
name: str("Optional operator name (used in the email greeting and on the CRM lead)."),
|
|
354
|
+
workspace_id: str("Optional workspace override. Defaults to the workspace just created."),
|
|
355
|
+
},
|
|
356
|
+
["email"],
|
|
357
|
+
),
|
|
358
|
+
handler: async (a) => {
|
|
359
|
+
const workspaceId = a.workspace_id ?? getDefaultWorkspace();
|
|
360
|
+
if (!workspaceId) {
|
|
361
|
+
throw new Error(
|
|
362
|
+
"No workspace selected. Run create_workspace({ name: '…' }) first, or pass workspace_id.",
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
const bearer = getWorkspaceBearer(workspaceId);
|
|
366
|
+
if (!bearer) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
`No local bearer token for workspace ${workspaceId}. This device did not create it. Re-run create_workspace or switch to the device that did.`,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Step 1: pull the workspace snapshot so we have the public URLs
|
|
373
|
+
// for the welcome email.
|
|
374
|
+
const snapshot = await api(
|
|
375
|
+
"GET",
|
|
376
|
+
`/workspace/${encodeURIComponent(workspaceId)}/snapshot`,
|
|
377
|
+
{ workspace_id: workspaceId },
|
|
378
|
+
);
|
|
379
|
+
const publicUrls = snapshot?.public_urls ?? {};
|
|
380
|
+
const slug = snapshot?.workspace?.slug ?? null;
|
|
381
|
+
if (!publicUrls.home || !publicUrls.book || !publicUrls.intake) {
|
|
382
|
+
throw new Error(
|
|
383
|
+
"Snapshot did not return public_urls (home/book/intake). Re-check the workspace.",
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
const appHost = API_INFO.base.replace(/\/api\/v1\/?$/, "");
|
|
387
|
+
const adminUrl = `${appHost}/admin/${encodeURIComponent(workspaceId)}?token=${encodeURIComponent(bearer)}`;
|
|
388
|
+
|
|
389
|
+
// Step 2: send the welcome email. Failures here are surfaced
|
|
390
|
+
// (operator told us their email, we owe them the email) but
|
|
391
|
+
// don't block the lead-capture step below.
|
|
392
|
+
let emailSent = false;
|
|
393
|
+
let emailError = null;
|
|
394
|
+
try {
|
|
395
|
+
await api("POST", "/email/send-welcome", {
|
|
396
|
+
body: {
|
|
397
|
+
email: a.email,
|
|
398
|
+
name: a.name ?? null,
|
|
399
|
+
workspace: {
|
|
400
|
+
landing_url: publicUrls.home,
|
|
401
|
+
booking_url: publicUrls.book,
|
|
402
|
+
intake_url: publicUrls.intake,
|
|
403
|
+
admin_url: adminUrl,
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
workspace_id: workspaceId,
|
|
407
|
+
});
|
|
408
|
+
emailSent = true;
|
|
409
|
+
} catch (err) {
|
|
410
|
+
emailError = err?.message ?? String(err);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Step 3: record the operator as a lead in SeldonFrame's own
|
|
414
|
+
// CRM workspace. Anonymous endpoint — no bearer required, ops
|
|
415
|
+
// workspace ID is server-side env only. Soft-failure: if the
|
|
416
|
+
// ops workspace isn't configured we still return ok.
|
|
417
|
+
let leadRecorded = false;
|
|
418
|
+
let leadId = null;
|
|
419
|
+
let leadError = null;
|
|
420
|
+
try {
|
|
421
|
+
const leadResp = await api("POST", "/leads/operator-signup", {
|
|
422
|
+
body: {
|
|
423
|
+
email: a.email,
|
|
424
|
+
name: a.name ?? null,
|
|
425
|
+
source_workspace_id: workspaceId,
|
|
426
|
+
source_workspace_slug: slug,
|
|
427
|
+
source: "mcp-onboarding",
|
|
428
|
+
},
|
|
429
|
+
allow_anonymous: true,
|
|
430
|
+
});
|
|
431
|
+
leadRecorded = Boolean(leadResp?.recorded);
|
|
432
|
+
leadId = leadResp?.lead_id ?? null;
|
|
433
|
+
} catch (err) {
|
|
434
|
+
leadError = err?.message ?? String(err);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
ok: emailSent || leadRecorded,
|
|
439
|
+
email_sent: emailSent,
|
|
440
|
+
email_error: emailError,
|
|
441
|
+
lead_recorded: leadRecorded,
|
|
442
|
+
lead_id: leadId,
|
|
443
|
+
lead_error: leadError,
|
|
444
|
+
message: emailSent
|
|
445
|
+
? `Welcome email sent to ${a.email}. Check your inbox — the admin URL is in there too.`
|
|
446
|
+
: `Could not send welcome email${emailError ? `: ${emailError}` : ""}. Lead ${leadRecorded ? "recorded" : "not recorded"}.`,
|
|
447
|
+
next: [
|
|
448
|
+
"configure_booking({ title, duration_minutes, description }) — tune the booking page if you collected business hours",
|
|
449
|
+
"customize_intake_form({ ... }) — match your intake to the workspace's lead-qualification questions",
|
|
450
|
+
"install_vertical_pack({ pack: '<industry>' }) — add domain-specific objects, fields, and views",
|
|
451
|
+
],
|
|
452
|
+
};
|
|
453
|
+
},
|
|
454
|
+
},
|
|
210
455
|
{
|
|
211
456
|
name: "revoke_bearer",
|
|
212
457
|
description:
|
|
@@ -394,10 +639,10 @@ export const TOOLS = [
|
|
|
394
639
|
{
|
|
395
640
|
name: "install_vertical_pack",
|
|
396
641
|
description:
|
|
397
|
-
"
|
|
642
|
+
"Set up an industry template (real-estate, dental, legal, …). Adds industry-specific objects, fields, and views to the CRM and pre-fills the booking page + intake form for that line of work.",
|
|
398
643
|
inputSchema: obj(
|
|
399
644
|
{
|
|
400
|
-
pack: str("
|
|
645
|
+
pack: str("Industry template name, e.g. 'real-estate', 'dental', 'legal'."),
|
|
401
646
|
workspace_id: str("Optional workspace override."),
|
|
402
647
|
},
|
|
403
648
|
["pack"],
|
|
@@ -413,10 +658,10 @@ export const TOOLS = [
|
|
|
413
658
|
{
|
|
414
659
|
name: "install_caldiy_booking",
|
|
415
660
|
description:
|
|
416
|
-
"Install the
|
|
661
|
+
"Install the booking page (event types, availability, scheduled bookings). Example: install_caldiy_booking({})",
|
|
417
662
|
inputSchema: obj({
|
|
418
663
|
workspace_id: str("Optional workspace override."),
|
|
419
|
-
config: { type: "object", description: "Optional
|
|
664
|
+
config: { type: "object", description: "Optional booking-page configuration overrides." },
|
|
420
665
|
}),
|
|
421
666
|
handler: async (a) => {
|
|
422
667
|
const ws = wsOrDefault(a.workspace_id);
|
|
@@ -429,10 +674,10 @@ export const TOOLS = [
|
|
|
429
674
|
{
|
|
430
675
|
name: "install_formbricks_intake",
|
|
431
676
|
description:
|
|
432
|
-
"Install
|
|
677
|
+
"Install an intake form (questions, conditional logic, automatic CRM sync). Example: install_formbricks_intake({})",
|
|
433
678
|
inputSchema: obj({
|
|
434
679
|
workspace_id: str("Optional workspace override."),
|
|
435
|
-
form_id: str("Optional existing
|
|
680
|
+
form_id: str("Optional existing intake-form id to bind."),
|
|
436
681
|
}),
|
|
437
682
|
handler: async (a) => {
|
|
438
683
|
const ws = wsOrDefault(a.workspace_id);
|
|
@@ -459,7 +704,7 @@ export const TOOLS = [
|
|
|
459
704
|
{
|
|
460
705
|
name: "fetch_source_for_soul",
|
|
461
706
|
description:
|
|
462
|
-
"Fetch a URL and return normalized text (headings + body, up to 256KB). Use this to gather raw content
|
|
707
|
+
"Fetch a URL and return normalized text (headings + body, up to 256KB). Use this to gather raw content from the operator's existing website; then extract a structured business profile and save it with submit_soul. Zero LLM cost to SeldonFrame — extraction runs in this session.",
|
|
463
708
|
inputSchema: obj(
|
|
464
709
|
{
|
|
465
710
|
url: str("Absolute URL to fetch."),
|
|
@@ -478,7 +723,7 @@ export const TOOLS = [
|
|
|
478
723
|
truncated,
|
|
479
724
|
text,
|
|
480
725
|
next: [
|
|
481
|
-
"Extract a
|
|
726
|
+
"Extract a business profile: { mission, audience, tone, offerings[], differentiators[], faqs[] }",
|
|
482
727
|
"submit_soul({ soul: <extracted> })",
|
|
483
728
|
],
|
|
484
729
|
};
|
|
@@ -487,13 +732,13 @@ export const TOOLS = [
|
|
|
487
732
|
{
|
|
488
733
|
name: "submit_soul",
|
|
489
734
|
description:
|
|
490
|
-
"
|
|
735
|
+
"Save a business profile to the active workspace. The profile drives the landing page, intake form copy, and AI-agent context. Call this after fetch_source_for_soul or after gathering details from the user. Triggers a re-render of the public landing page so changes are visible immediately.",
|
|
491
736
|
inputSchema: obj(
|
|
492
737
|
{
|
|
493
738
|
soul: {
|
|
494
739
|
type: "object",
|
|
495
740
|
description:
|
|
496
|
-
"
|
|
741
|
+
"Business profile. Expected keys: business_name, tagline, soul_description, phone, email, address, offerings, faqs, testimonials. Additional keys allowed — they're preserved for future use.",
|
|
497
742
|
},
|
|
498
743
|
workspace_id: str("Optional workspace override."),
|
|
499
744
|
},
|
package/src/welcome.js
CHANGED
|
@@ -1,132 +1,130 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
-
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
export const FIRST_CALL_BANNER = `🌑 Welcome to SeldonFrame. Your workspace is live — every URL above works right now. From here on, every tool response will include a \`next:\` array; follow it and you'll have a production-ready Business OS in under a minute.`;
|
|
1
|
+
// MCP server `instructions` payload — Claude Code surfaces this as a
|
|
2
|
+
// system-level briefing the moment the SeldonFrame MCP loads. Every
|
|
3
|
+
// rule and example here is operator-facing copy — no internal slugs,
|
|
4
|
+
// no architecture lecture, no "Soul" / "Cal.diy" / "Formbricks" /
|
|
5
|
+
// "Brain v2" jargon.
|
|
6
|
+
|
|
7
|
+
export const VERSION = "1.0.6";
|
|
8
|
+
|
|
9
|
+
export const WELCOME_MARKDOWN = `# SeldonFrame — create a real Business OS in one conversation
|
|
10
|
+
|
|
11
|
+
SeldonFrame creates live, hosted business systems for service
|
|
12
|
+
businesses, agencies, coaches, and SaaS founders. One conversation
|
|
13
|
+
gives the operator a public website, booking page, intake form,
|
|
14
|
+
CRM, and AI agents — all on a real subdomain.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## NEVER CREATE LOCAL FILES
|
|
19
|
+
|
|
20
|
+
SeldonFrame workspaces are created via the \`create_workspace\` MCP
|
|
21
|
+
tool. They are NOT created by writing files on disk. Specifically:
|
|
22
|
+
|
|
23
|
+
- Do NOT create a \`soul.json\` file or any other local file.
|
|
24
|
+
- Do NOT call \`loadSoulPackage\` or any local function.
|
|
25
|
+
- Do NOT scaffold a project directory.
|
|
26
|
+
- Do NOT modify files in the current working directory.
|
|
27
|
+
|
|
28
|
+
If you are running inside a SeldonFrame source-code repository and
|
|
29
|
+
the user asks for a workspace, IGNORE the local code — call the
|
|
30
|
+
\`create_workspace\` MCP tool. The tool returns live URLs on
|
|
31
|
+
\`<slug>.app.seldonframe.com\` that work in any browser within
|
|
32
|
+
seconds. There is no local-file path. This is the only correct
|
|
33
|
+
flow, regardless of what directory Claude Code is running from.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## The full happy path (4 turns)
|
|
38
|
+
|
|
39
|
+
1. **Ask for the business details.** When the user says "create a
|
|
40
|
+
workspace," gather these conversationally — one or two
|
|
41
|
+
questions per turn, not all at once:
|
|
42
|
+
- Business name
|
|
43
|
+
- Industry (HVAC, dental, legal, coaching, real-estate, agency, …)
|
|
44
|
+
- Phone number (for local services — for SaaS skip)
|
|
45
|
+
- Top 3-5 services / products
|
|
46
|
+
- Brief description (1 sentence)
|
|
47
|
+
|
|
48
|
+
2. **Create the workspace.** Call \`create_workspace\` with the
|
|
49
|
+
structured fields:
|
|
50
|
+
\`\`\`
|
|
51
|
+
create_workspace({
|
|
52
|
+
name: "Precision Plumbing Co",
|
|
53
|
+
phone: "(555) 123-4567",
|
|
54
|
+
business_description: "Family-owned residential plumbing in Austin.",
|
|
55
|
+
services: [
|
|
56
|
+
{ name: "Drain Cleaning" },
|
|
57
|
+
{ name: "Water Heater Repair" },
|
|
58
|
+
{ name: "Leak Detection" }
|
|
59
|
+
]
|
|
60
|
+
})
|
|
61
|
+
\`\`\`
|
|
62
|
+
The response includes live URLs (website, booking, intake form,
|
|
63
|
+
admin dashboard). Show those URLs to the operator. The admin
|
|
64
|
+
URL is bearer-token-scoped and expires in 7 days — that's why
|
|
65
|
+
step 4 matters.
|
|
66
|
+
|
|
67
|
+
3. **Ask for the operator's email.** This is the keystone of the
|
|
68
|
+
onboarding loop. The email becomes their account. Ask:
|
|
69
|
+
> "What's your email? I'll send you all those links so you
|
|
70
|
+
> don't lose them, and set up your admin login."
|
|
71
|
+
|
|
72
|
+
4. **Lock in the email.** Call \`collect_operator_email\` with the
|
|
73
|
+
email they gave you:
|
|
74
|
+
\`\`\`
|
|
75
|
+
collect_operator_email({ email: "max@precisionplumbing.com", name: "Max" })
|
|
76
|
+
\`\`\`
|
|
77
|
+
This sends the welcome email + creates their account so the
|
|
78
|
+
admin URL keeps working past the 7-day token window.
|
|
79
|
+
|
|
80
|
+
After that, the operator can customize their workspace through
|
|
81
|
+
further natural-language requests ("change the headline to …",
|
|
82
|
+
"add an FAQ section", "set up an industry template for plumbing")
|
|
83
|
+
— each routes to a typed MCP tool.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## What the tools do (operator language only)
|
|
88
|
+
|
|
89
|
+
- **\`create_workspace\`** — creates the live business OS (website,
|
|
90
|
+
booking, intake form, CRM, AI agents). Always the first call.
|
|
91
|
+
- **\`collect_operator_email\`** — sends the welcome email + sets up
|
|
92
|
+
the operator's admin login. Always the second call.
|
|
93
|
+
- **\`update_landing_content\`** / **\`update_landing_section\`** —
|
|
94
|
+
edit the website's headline, subhead, sections, copy.
|
|
95
|
+
- **\`update_theme\`** — change colors, fonts, dark/light mode.
|
|
96
|
+
- **\`update_form\`** — edit the intake form's questions.
|
|
97
|
+
- **\`update_appointment_type\`** — edit the booking page's slot length,
|
|
98
|
+
title, description.
|
|
99
|
+
- **\`install_vertical_pack\`** — set up an industry template
|
|
100
|
+
(real-estate, dental, legal, plumbing, …).
|
|
101
|
+
- **\`list_contacts\`** / **\`create_contact\`** / **\`update_contact\`** —
|
|
102
|
+
manage the CRM.
|
|
103
|
+
- **\`list_deals\`** / **\`create_deal\`** / **\`move_deal_stage\`** —
|
|
104
|
+
manage the pipeline.
|
|
105
|
+
- **\`send_email\`** / **\`send_sms\`** — send messages from the
|
|
106
|
+
workspace's connected channels.
|
|
107
|
+
|
|
108
|
+
The full tool list is available via the MCP \`tools/list\` request.
|
|
109
|
+
Use whatever fits the operator's natural-language request.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Pricing
|
|
114
|
+
|
|
115
|
+
- **Free** — first workspace, free forever, no credit card.
|
|
116
|
+
- **Growth ($29/mo)** — up to 3 workspaces, custom domains,
|
|
117
|
+
white-label, metered AI usage.
|
|
118
|
+
- **Scale ($99/mo)** — unlimited workspaces, advanced AI features,
|
|
119
|
+
priority support.
|
|
120
|
+
|
|
121
|
+
Operators can upgrade via \`/settings/billing\` once they're in the
|
|
122
|
+
admin dashboard. Pre-fills their email automatically.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
**Docs:** <https://seldonframe.com/docs> · **Homepage:**
|
|
127
|
+
<https://seldonframe.com> · **Discord:** <https://discord.gg/sbVUu976NW>
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
export const FIRST_CALL_BANNER = `🚀 SeldonFrame is connected. Ready to create a live business OS — every URL the create_workspace tool returns is real and works in any browser within seconds. NEVER create local files; always use the MCP tools.`;
|