@seldonframe/mcp 1.0.3 → 1.0.5
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 +273 -19
- package/src/welcome.js +3 -3
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 CRM, booking, intake, and
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "MCP server for SeldonFrame — AI-native Business OS platform. One command creates a real hosted workspace with CRM, booking page, intake form, and AI agents. v1.0.5: collect_operator_email tool closes the onboarding loop (welcome email + lead capture); auto-injects x-org-id from workspace bearer for ALL tool calls (fixes list_contacts / list_deals / list_bookings / send_welcome_email); structured Soul-seed fields on create_workspace (phone, services, testimonials) so the landing page renders real data on the first GET; operator-facing labels replace internal slugs in responses.",
|
|
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,61 @@ export const TOOLS = [
|
|
|
41
42
|
{
|
|
42
43
|
name: "create_workspace",
|
|
43
44
|
description:
|
|
44
|
-
"Create a real, hosted workspace on <slug>.app.seldonframe.com with CRM,
|
|
45
|
+
"Create a real, hosted workspace on <slug>.app.seldonframe.com with a CRM, booking page, intake form, and AI agents pre-installed. The first workspace 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. Example: create_workspace({ name: 'Desert Cool HVAC', phone: '(602) 555-0188', services: [{ name: 'AC Repair' }, { name: 'Heating' }] })",
|
|
45
46
|
inputSchema: obj(
|
|
46
47
|
{
|
|
47
48
|
name: str("Human-readable workspace name."),
|
|
48
|
-
source: str("Optional URL or description to seed the
|
|
49
|
+
source: str("Optional URL or description to seed the business profile from."),
|
|
50
|
+
phone: str("Operator's business phone (any format — we render as-is). Renders in nav, hero, and footer when set."),
|
|
51
|
+
email: str("Optional contact email surfaced in the landing footer."),
|
|
52
|
+
address: str("Optional business address. Comma-separated street, city, region, postal, country renders as-is."),
|
|
53
|
+
tagline: str("Short hero tagline (one line)."),
|
|
54
|
+
business_description: str("One paragraph about the business (used for hero subhead + about section)."),
|
|
55
|
+
services: {
|
|
56
|
+
type: "array",
|
|
57
|
+
description:
|
|
58
|
+
"List of services / products / offerings the business provides. Renders as the services grid on the landing page. Each item: { name, description? }.",
|
|
59
|
+
items: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {
|
|
62
|
+
name: { type: "string" },
|
|
63
|
+
description: { type: "string" },
|
|
64
|
+
},
|
|
65
|
+
required: ["name"],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
testimonials: {
|
|
69
|
+
type: "array",
|
|
70
|
+
description:
|
|
71
|
+
"Optional customer testimonials to seed the landing page's testimonials section. Each item: { quote, name?, role?, company? }.",
|
|
72
|
+
items: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
quote: { type: "string" },
|
|
76
|
+
name: { type: "string" },
|
|
77
|
+
role: { type: "string" },
|
|
78
|
+
company: { type: "string" },
|
|
79
|
+
},
|
|
80
|
+
required: ["quote"],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
49
83
|
},
|
|
50
84
|
["name"],
|
|
51
85
|
),
|
|
52
86
|
handler: async (args) => {
|
|
53
87
|
const firstEver = isFirstEverCall();
|
|
54
88
|
const result = await api("POST", "/workspace/create", {
|
|
55
|
-
body: {
|
|
89
|
+
body: {
|
|
90
|
+
name: args.name,
|
|
91
|
+
source: args.source ?? null,
|
|
92
|
+
phone: args.phone ?? null,
|
|
93
|
+
email: args.email ?? null,
|
|
94
|
+
address: args.address ?? null,
|
|
95
|
+
tagline: args.tagline ?? null,
|
|
96
|
+
business_description: args.business_description ?? null,
|
|
97
|
+
services: args.services ?? null,
|
|
98
|
+
testimonials: args.testimonials ?? null,
|
|
99
|
+
},
|
|
56
100
|
allow_anonymous: true,
|
|
57
101
|
});
|
|
58
102
|
const ws = result.workspace ?? result;
|
|
@@ -67,6 +111,16 @@ export const TOOLS = [
|
|
|
67
111
|
// in the response. Operators paste it into their browser and land
|
|
68
112
|
// directly on the dashboard — no signup, no login, no OAuth.
|
|
69
113
|
const adminUrl = result.admin_url ?? null;
|
|
114
|
+
// v1.0.4: strip the misleading `admin_*` keys out of the legacy
|
|
115
|
+
// flat `urls` object so old clients don't surface the
|
|
116
|
+
// /switch-workspace login-required URLs as if they were the
|
|
117
|
+
// intended path. Public-facing keys (home/book/intake) stay.
|
|
118
|
+
const rawUrls = result.urls ?? ws.urls ?? null;
|
|
119
|
+
const publicUrls = rawUrls
|
|
120
|
+
? Object.fromEntries(
|
|
121
|
+
Object.entries(rawUrls).filter(([key]) => !key.startsWith("admin_")),
|
|
122
|
+
)
|
|
123
|
+
: null;
|
|
70
124
|
const payload = {
|
|
71
125
|
ok: true,
|
|
72
126
|
workspace: {
|
|
@@ -82,15 +136,43 @@ export const TOOLS = [
|
|
|
82
136
|
admin_url_message: adminUrl
|
|
83
137
|
? `⚡ 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.`
|
|
84
138
|
: null,
|
|
85
|
-
urls:
|
|
86
|
-
public_urls: result.public_urls ??
|
|
87
|
-
|
|
139
|
+
urls: publicUrls,
|
|
140
|
+
public_urls: result.public_urls ?? publicUrls,
|
|
141
|
+
// May 1, 2026 — installed-block labels translated to operator
|
|
142
|
+
// copy. The MCP-internal slugs ("caldiy-booking",
|
|
143
|
+
// "formbricks-intake", "brain-v2") are deployment details no
|
|
144
|
+
// operator should need to learn — surfacing them on every
|
|
145
|
+
// create_workspace turn was friction without payoff.
|
|
146
|
+
installed: (result.installed ?? [
|
|
147
|
+
"crm",
|
|
148
|
+
"caldiy-booking",
|
|
149
|
+
"formbricks-intake",
|
|
150
|
+
"brain-v2",
|
|
151
|
+
]).map((slug) => {
|
|
152
|
+
if (slug === "caldiy-booking") return "Booking page";
|
|
153
|
+
if (slug === "formbricks-intake") return "Intake form";
|
|
154
|
+
if (slug === "brain-v2") return "AI learning";
|
|
155
|
+
if (slug === "crm") return "CRM";
|
|
156
|
+
return slug;
|
|
157
|
+
}),
|
|
158
|
+
// May 1, 2026 — email collection promoted to the first
|
|
159
|
+
// suggested next step. Per the onboarding-loop spec, the
|
|
160
|
+
// operator gets a permanent inbox record (admin URLs,
|
|
161
|
+
// landing/booking/intake links) AND we earn a CRM lead in
|
|
162
|
+
// SeldonFrame's own workspace, so every workspace creation
|
|
163
|
+
// becomes a tracked relationship.
|
|
164
|
+
prompt_for_email: {
|
|
165
|
+
message:
|
|
166
|
+
"What's your email? I'll send you a summary with all the links above (so you don't lose them) and use it for your admin login when you upgrade. — calls collect_operator_email({ email, name? })",
|
|
167
|
+
tool: "collect_operator_email",
|
|
168
|
+
},
|
|
88
169
|
next: [
|
|
89
170
|
adminUrl
|
|
90
|
-
?
|
|
171
|
+
? `⚡ Admin Dashboard (bookmark this!): ${adminUrl} (paste into your browser; no signup needed; token expires in 7 days)`
|
|
91
172
|
: null,
|
|
92
|
-
"
|
|
93
|
-
"
|
|
173
|
+
"Ask the user for their email, then call collect_operator_email({ email, name? }) — sends the welcome email + creates a permanent record. Do this BEFORE any further customization.",
|
|
174
|
+
"install_vertical_pack({ pack: '<industry>' }) — set up an industry template (real-estate, dental, legal, …)",
|
|
175
|
+
"fetch_source_for_soul({ url: 'https://yoursite.com' }) → submit_soul({ soul }) — pull the business profile from a website",
|
|
94
176
|
"get_workspace_snapshot({}) — read workspace state to reason about next steps",
|
|
95
177
|
].filter(Boolean),
|
|
96
178
|
};
|
|
@@ -197,6 +279,178 @@ export const TOOLS = [
|
|
|
197
279
|
};
|
|
198
280
|
},
|
|
199
281
|
},
|
|
282
|
+
{
|
|
283
|
+
name: "send_welcome_email",
|
|
284
|
+
description:
|
|
285
|
+
"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' }).",
|
|
286
|
+
inputSchema: obj(
|
|
287
|
+
{
|
|
288
|
+
email: str("Recipient email address."),
|
|
289
|
+
name: str("Optional recipient name (used in the greeting)."),
|
|
290
|
+
workspace_id: str("Optional workspace override. Defaults to active workspace."),
|
|
291
|
+
},
|
|
292
|
+
["email"],
|
|
293
|
+
),
|
|
294
|
+
handler: async (a) => {
|
|
295
|
+
const workspaceId = a.workspace_id ?? getDefaultWorkspace();
|
|
296
|
+
if (!workspaceId) {
|
|
297
|
+
throw new Error(
|
|
298
|
+
"No workspace selected. Run create_workspace({ name: '…' }) first, or pass workspace_id.",
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
const bearer = getWorkspaceBearer(workspaceId);
|
|
302
|
+
if (!bearer) {
|
|
303
|
+
throw new Error(
|
|
304
|
+
`No local bearer token for workspace ${workspaceId}. This device did not create it. Re-run create_workspace or switch to the device that did.`,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const snapshot = await api(
|
|
309
|
+
"GET",
|
|
310
|
+
`/workspace/${encodeURIComponent(workspaceId)}/snapshot`,
|
|
311
|
+
{ workspace_id: workspaceId },
|
|
312
|
+
);
|
|
313
|
+
const publicUrls = snapshot?.public_urls ?? {};
|
|
314
|
+
if (!publicUrls.home || !publicUrls.book || !publicUrls.intake) {
|
|
315
|
+
throw new Error(
|
|
316
|
+
"Snapshot did not return public_urls (home/book/intake). Re-check the workspace.",
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Construct the bearer-scoped admin URL from the API base.
|
|
321
|
+
// API_INFO.base is `<host>/api/v1` — strip that suffix to get the app host.
|
|
322
|
+
const appHost = API_INFO.base.replace(/\/api\/v1\/?$/, "");
|
|
323
|
+
const adminUrl = `${appHost}/admin/${encodeURIComponent(workspaceId)}?token=${encodeURIComponent(bearer)}`;
|
|
324
|
+
|
|
325
|
+
await api("POST", "/email/send-welcome", {
|
|
326
|
+
body: {
|
|
327
|
+
email: a.email,
|
|
328
|
+
name: a.name ?? null,
|
|
329
|
+
workspace: {
|
|
330
|
+
landing_url: publicUrls.home,
|
|
331
|
+
booking_url: publicUrls.book,
|
|
332
|
+
intake_url: publicUrls.intake,
|
|
333
|
+
admin_url: adminUrl,
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
workspace_id: workspaceId,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
ok: true,
|
|
341
|
+
message: `Welcome email sent to ${a.email}`,
|
|
342
|
+
};
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: "collect_operator_email",
|
|
347
|
+
description:
|
|
348
|
+
"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' })",
|
|
349
|
+
inputSchema: obj(
|
|
350
|
+
{
|
|
351
|
+
email: str("Operator email — used as the welcome email recipient AND as the unique key for the SeldonFrame CRM lead."),
|
|
352
|
+
name: str("Optional operator name (used in the email greeting and on the CRM lead)."),
|
|
353
|
+
workspace_id: str("Optional workspace override. Defaults to the workspace just created."),
|
|
354
|
+
},
|
|
355
|
+
["email"],
|
|
356
|
+
),
|
|
357
|
+
handler: async (a) => {
|
|
358
|
+
const workspaceId = a.workspace_id ?? getDefaultWorkspace();
|
|
359
|
+
if (!workspaceId) {
|
|
360
|
+
throw new Error(
|
|
361
|
+
"No workspace selected. Run create_workspace({ name: '…' }) first, or pass workspace_id.",
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
const bearer = getWorkspaceBearer(workspaceId);
|
|
365
|
+
if (!bearer) {
|
|
366
|
+
throw new Error(
|
|
367
|
+
`No local bearer token for workspace ${workspaceId}. This device did not create it. Re-run create_workspace or switch to the device that did.`,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Step 1: pull the workspace snapshot so we have the public URLs
|
|
372
|
+
// for the welcome email.
|
|
373
|
+
const snapshot = await api(
|
|
374
|
+
"GET",
|
|
375
|
+
`/workspace/${encodeURIComponent(workspaceId)}/snapshot`,
|
|
376
|
+
{ workspace_id: workspaceId },
|
|
377
|
+
);
|
|
378
|
+
const publicUrls = snapshot?.public_urls ?? {};
|
|
379
|
+
const slug = snapshot?.workspace?.slug ?? null;
|
|
380
|
+
if (!publicUrls.home || !publicUrls.book || !publicUrls.intake) {
|
|
381
|
+
throw new Error(
|
|
382
|
+
"Snapshot did not return public_urls (home/book/intake). Re-check the workspace.",
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
const appHost = API_INFO.base.replace(/\/api\/v1\/?$/, "");
|
|
386
|
+
const adminUrl = `${appHost}/admin/${encodeURIComponent(workspaceId)}?token=${encodeURIComponent(bearer)}`;
|
|
387
|
+
|
|
388
|
+
// Step 2: send the welcome email. Failures here are surfaced
|
|
389
|
+
// (operator told us their email, we owe them the email) but
|
|
390
|
+
// don't block the lead-capture step below.
|
|
391
|
+
let emailSent = false;
|
|
392
|
+
let emailError = null;
|
|
393
|
+
try {
|
|
394
|
+
await api("POST", "/email/send-welcome", {
|
|
395
|
+
body: {
|
|
396
|
+
email: a.email,
|
|
397
|
+
name: a.name ?? null,
|
|
398
|
+
workspace: {
|
|
399
|
+
landing_url: publicUrls.home,
|
|
400
|
+
booking_url: publicUrls.book,
|
|
401
|
+
intake_url: publicUrls.intake,
|
|
402
|
+
admin_url: adminUrl,
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
workspace_id: workspaceId,
|
|
406
|
+
});
|
|
407
|
+
emailSent = true;
|
|
408
|
+
} catch (err) {
|
|
409
|
+
emailError = err?.message ?? String(err);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Step 3: record the operator as a lead in SeldonFrame's own
|
|
413
|
+
// CRM workspace. Anonymous endpoint — no bearer required, ops
|
|
414
|
+
// workspace ID is server-side env only. Soft-failure: if the
|
|
415
|
+
// ops workspace isn't configured we still return ok.
|
|
416
|
+
let leadRecorded = false;
|
|
417
|
+
let leadId = null;
|
|
418
|
+
let leadError = null;
|
|
419
|
+
try {
|
|
420
|
+
const leadResp = await api("POST", "/leads/operator-signup", {
|
|
421
|
+
body: {
|
|
422
|
+
email: a.email,
|
|
423
|
+
name: a.name ?? null,
|
|
424
|
+
source_workspace_id: workspaceId,
|
|
425
|
+
source_workspace_slug: slug,
|
|
426
|
+
source: "mcp-onboarding",
|
|
427
|
+
},
|
|
428
|
+
allow_anonymous: true,
|
|
429
|
+
});
|
|
430
|
+
leadRecorded = Boolean(leadResp?.recorded);
|
|
431
|
+
leadId = leadResp?.lead_id ?? null;
|
|
432
|
+
} catch (err) {
|
|
433
|
+
leadError = err?.message ?? String(err);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
ok: emailSent || leadRecorded,
|
|
438
|
+
email_sent: emailSent,
|
|
439
|
+
email_error: emailError,
|
|
440
|
+
lead_recorded: leadRecorded,
|
|
441
|
+
lead_id: leadId,
|
|
442
|
+
lead_error: leadError,
|
|
443
|
+
message: emailSent
|
|
444
|
+
? `Welcome email sent to ${a.email}. Check your inbox — the admin URL is in there too.`
|
|
445
|
+
: `Could not send welcome email${emailError ? `: ${emailError}` : ""}. Lead ${leadRecorded ? "recorded" : "not recorded"}.`,
|
|
446
|
+
next: [
|
|
447
|
+
"configure_booking({ title, duration_minutes, description }) — tune the booking page if you collected business hours",
|
|
448
|
+
"customize_intake_form({ ... }) — match your intake to the workspace's lead-qualification questions",
|
|
449
|
+
"install_vertical_pack({ pack: '<industry>' }) — add domain-specific objects, fields, and views",
|
|
450
|
+
],
|
|
451
|
+
};
|
|
452
|
+
},
|
|
453
|
+
},
|
|
200
454
|
{
|
|
201
455
|
name: "revoke_bearer",
|
|
202
456
|
description:
|
|
@@ -384,10 +638,10 @@ export const TOOLS = [
|
|
|
384
638
|
{
|
|
385
639
|
name: "install_vertical_pack",
|
|
386
640
|
description:
|
|
387
|
-
"
|
|
641
|
+
"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.",
|
|
388
642
|
inputSchema: obj(
|
|
389
643
|
{
|
|
390
|
-
pack: str("
|
|
644
|
+
pack: str("Industry template name, e.g. 'real-estate', 'dental', 'legal'."),
|
|
391
645
|
workspace_id: str("Optional workspace override."),
|
|
392
646
|
},
|
|
393
647
|
["pack"],
|
|
@@ -403,10 +657,10 @@ export const TOOLS = [
|
|
|
403
657
|
{
|
|
404
658
|
name: "install_caldiy_booking",
|
|
405
659
|
description:
|
|
406
|
-
"Install the
|
|
660
|
+
"Install the booking page (event types, availability, scheduled bookings). Example: install_caldiy_booking({})",
|
|
407
661
|
inputSchema: obj({
|
|
408
662
|
workspace_id: str("Optional workspace override."),
|
|
409
|
-
config: { type: "object", description: "Optional
|
|
663
|
+
config: { type: "object", description: "Optional booking-page configuration overrides." },
|
|
410
664
|
}),
|
|
411
665
|
handler: async (a) => {
|
|
412
666
|
const ws = wsOrDefault(a.workspace_id);
|
|
@@ -419,10 +673,10 @@ export const TOOLS = [
|
|
|
419
673
|
{
|
|
420
674
|
name: "install_formbricks_intake",
|
|
421
675
|
description:
|
|
422
|
-
"Install
|
|
676
|
+
"Install an intake form (questions, conditional logic, automatic CRM sync). Example: install_formbricks_intake({})",
|
|
423
677
|
inputSchema: obj({
|
|
424
678
|
workspace_id: str("Optional workspace override."),
|
|
425
|
-
form_id: str("Optional existing
|
|
679
|
+
form_id: str("Optional existing intake-form id to bind."),
|
|
426
680
|
}),
|
|
427
681
|
handler: async (a) => {
|
|
428
682
|
const ws = wsOrDefault(a.workspace_id);
|
|
@@ -449,7 +703,7 @@ export const TOOLS = [
|
|
|
449
703
|
{
|
|
450
704
|
name: "fetch_source_for_soul",
|
|
451
705
|
description:
|
|
452
|
-
"Fetch a URL and return normalized text (headings + body, up to 256KB). Use this to gather raw content
|
|
706
|
+
"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.",
|
|
453
707
|
inputSchema: obj(
|
|
454
708
|
{
|
|
455
709
|
url: str("Absolute URL to fetch."),
|
|
@@ -468,7 +722,7 @@ export const TOOLS = [
|
|
|
468
722
|
truncated,
|
|
469
723
|
text,
|
|
470
724
|
next: [
|
|
471
|
-
"Extract a
|
|
725
|
+
"Extract a business profile: { mission, audience, tone, offerings[], differentiators[], faqs[] }",
|
|
472
726
|
"submit_soul({ soul: <extracted> })",
|
|
473
727
|
],
|
|
474
728
|
};
|
|
@@ -477,13 +731,13 @@ export const TOOLS = [
|
|
|
477
731
|
{
|
|
478
732
|
name: "submit_soul",
|
|
479
733
|
description:
|
|
480
|
-
"
|
|
734
|
+
"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.",
|
|
481
735
|
inputSchema: obj(
|
|
482
736
|
{
|
|
483
737
|
soul: {
|
|
484
738
|
type: "object",
|
|
485
739
|
description:
|
|
486
|
-
"
|
|
740
|
+
"Business profile. Expected keys: business_name, tagline, soul_description, phone, email, address, offerings, faqs, testimonials. Additional keys allowed — they're preserved for future use.",
|
|
487
741
|
},
|
|
488
742
|
workspace_id: str("Optional workspace override."),
|
|
489
743
|
},
|
package/src/welcome.js
CHANGED
|
@@ -104,9 +104,9 @@ Soul compilation runs HERE, not on the backend:
|
|
|
104
104
|
|
|
105
105
|
## When you'll need \`SELDONFRAME_API_KEY\`
|
|
106
106
|
|
|
107
|
-
The first workspace is free forever. Paid tiers (
|
|
108
|
-
$99/mo,
|
|
109
|
-
white-label, and advanced Brain capabilities. A key is required for:
|
|
107
|
+
The first workspace is free forever. Paid tiers (Growth $29/mo, Scale
|
|
108
|
+
$99/mo, both with metered usage) unlock additional workspaces, custom
|
|
109
|
+
domains, white-label, and advanced Brain capabilities. A key is required for:
|
|
110
110
|
|
|
111
111
|
- Adding a **second workspace**
|
|
112
112
|
- Connecting a **custom domain**
|