@seldonframe/mcp 1.0.1 → 1.0.3
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/index.js +81 -81
- package/src/tools.js +61 -3
- package/src/welcome.js +61 -9
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 Brain v2.",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "MCP server for SeldonFrame — AI-native Business OS platform. One command creates a real hosted workspace with CRM, booking, intake, and Brain v2. v1.0.3: surfaces admin URL prominently in create_workspace response (no signup required to access dashboard).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"seldonframe-mcp": "src/index.js"
|
package/src/index.js
CHANGED
|
@@ -1,81 +1,81 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// Node version gate — must run before any imports that could fail or
|
|
4
|
-
// reach for fetch(). Without this, Node 16 users get a cryptic
|
|
5
|
-
// "fetch is not defined" crash the moment the first tool calls the
|
|
6
|
-
// SeldonFrame API.
|
|
7
|
-
//
|
|
8
|
-
// Per L-29 the cleanroom test in GitHub Codespaces (default image
|
|
9
|
-
// ships Node v16.20.2 in some configurations) surfaced this
|
|
10
|
-
// immediately on @seldonframe/mcp@1.0.0. Strict requirement is
|
|
11
|
-
// documented in engines.node = ">=18" and reinforced here at runtime
|
|
12
|
-
// so users get actionable next-steps instead of a stack trace.
|
|
13
|
-
//
|
|
14
|
-
// Implementation note: we use dynamic `await import()` for the SDK
|
|
15
|
-
// modules below specifically so this gate runs BEFORE any SDK code
|
|
16
|
-
// executes. Static `import` statements at the top of an ESM module
|
|
17
|
-
// are hoisted and evaluated before any module-body code, which would
|
|
18
|
-
// defeat the gate if the SDK ever started failing at import-time on
|
|
19
|
-
// older Node versions.
|
|
20
|
-
const [nodeMajor] = process.versions.node.split(".").map(Number);
|
|
21
|
-
if (nodeMajor < 18) {
|
|
22
|
-
process.stderr.write(
|
|
23
|
-
`\n SeldonFrame MCP requires Node.js 18 or later.\n` +
|
|
24
|
-
` You are running Node.js ${process.versions.node}.\n\n` +
|
|
25
|
-
` To fix:\n` +
|
|
26
|
-
` nvm install 18 && nvm use 18\n` +
|
|
27
|
-
` Or:\n` +
|
|
28
|
-
` nvm install 20 && nvm use 20\n\n` +
|
|
29
|
-
` Then retry:\n` +
|
|
30
|
-
` claude mcp add seldonframe -- npx -y @seldonframe/mcp\n\n`,
|
|
31
|
-
);
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
|
|
36
|
-
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
37
|
-
const { CallToolRequestSchema, ListToolsRequestSchema } = await import(
|
|
38
|
-
"@modelcontextprotocol/sdk/types.js"
|
|
39
|
-
);
|
|
40
|
-
const { WELCOME_MARKDOWN, VERSION } = await import("./welcome.js");
|
|
41
|
-
const { TOOLS, TOOL_MAP } = await import("./tools.js");
|
|
42
|
-
|
|
43
|
-
const server = new Server(
|
|
44
|
-
{ name: "seldonframe", version: VERSION },
|
|
45
|
-
{
|
|
46
|
-
capabilities: { tools: {} },
|
|
47
|
-
instructions: WELCOME_MARKDOWN,
|
|
48
|
-
},
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
52
|
-
tools: TOOLS.map(({ name, description, inputSchema }) => ({
|
|
53
|
-
name,
|
|
54
|
-
description,
|
|
55
|
-
inputSchema,
|
|
56
|
-
})),
|
|
57
|
-
}));
|
|
58
|
-
|
|
59
|
-
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
60
|
-
const tool = TOOL_MAP[req.params.name];
|
|
61
|
-
if (!tool) {
|
|
62
|
-
return {
|
|
63
|
-
isError: true,
|
|
64
|
-
content: [{ type: "text", text: `Unknown tool: ${req.params.name}` }],
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
try {
|
|
68
|
-
const result = await tool.handler(req.params.arguments ?? {});
|
|
69
|
-
return {
|
|
70
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
71
|
-
};
|
|
72
|
-
} catch (err) {
|
|
73
|
-
return {
|
|
74
|
-
isError: true,
|
|
75
|
-
content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const transport = new StdioServerTransport();
|
|
81
|
-
await server.connect(transport);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Node version gate — must run before any imports that could fail or
|
|
4
|
+
// reach for fetch(). Without this, Node 16 users get a cryptic
|
|
5
|
+
// "fetch is not defined" crash the moment the first tool calls the
|
|
6
|
+
// SeldonFrame API.
|
|
7
|
+
//
|
|
8
|
+
// Per L-29 the cleanroom test in GitHub Codespaces (default image
|
|
9
|
+
// ships Node v16.20.2 in some configurations) surfaced this
|
|
10
|
+
// immediately on @seldonframe/mcp@1.0.0. Strict requirement is
|
|
11
|
+
// documented in engines.node = ">=18" and reinforced here at runtime
|
|
12
|
+
// so users get actionable next-steps instead of a stack trace.
|
|
13
|
+
//
|
|
14
|
+
// Implementation note: we use dynamic `await import()` for the SDK
|
|
15
|
+
// modules below specifically so this gate runs BEFORE any SDK code
|
|
16
|
+
// executes. Static `import` statements at the top of an ESM module
|
|
17
|
+
// are hoisted and evaluated before any module-body code, which would
|
|
18
|
+
// defeat the gate if the SDK ever started failing at import-time on
|
|
19
|
+
// older Node versions.
|
|
20
|
+
const [nodeMajor] = process.versions.node.split(".").map(Number);
|
|
21
|
+
if (nodeMajor < 18) {
|
|
22
|
+
process.stderr.write(
|
|
23
|
+
`\n SeldonFrame MCP requires Node.js 18 or later.\n` +
|
|
24
|
+
` You are running Node.js ${process.versions.node}.\n\n` +
|
|
25
|
+
` To fix:\n` +
|
|
26
|
+
` nvm install 18 && nvm use 18\n` +
|
|
27
|
+
` Or:\n` +
|
|
28
|
+
` nvm install 20 && nvm use 20\n\n` +
|
|
29
|
+
` Then retry:\n` +
|
|
30
|
+
` claude mcp add seldonframe -- npx -y @seldonframe/mcp\n\n`,
|
|
31
|
+
);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
|
|
36
|
+
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
37
|
+
const { CallToolRequestSchema, ListToolsRequestSchema } = await import(
|
|
38
|
+
"@modelcontextprotocol/sdk/types.js"
|
|
39
|
+
);
|
|
40
|
+
const { WELCOME_MARKDOWN, VERSION } = await import("./welcome.js");
|
|
41
|
+
const { TOOLS, TOOL_MAP } = await import("./tools.js");
|
|
42
|
+
|
|
43
|
+
const server = new Server(
|
|
44
|
+
{ name: "seldonframe", version: VERSION },
|
|
45
|
+
{
|
|
46
|
+
capabilities: { tools: {} },
|
|
47
|
+
instructions: WELCOME_MARKDOWN,
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
52
|
+
tools: TOOLS.map(({ name, description, inputSchema }) => ({
|
|
53
|
+
name,
|
|
54
|
+
description,
|
|
55
|
+
inputSchema,
|
|
56
|
+
})),
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
60
|
+
const tool = TOOL_MAP[req.params.name];
|
|
61
|
+
if (!tool) {
|
|
62
|
+
return {
|
|
63
|
+
isError: true,
|
|
64
|
+
content: [{ type: "text", text: `Unknown tool: ${req.params.name}` }],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const result = await tool.handler(req.params.arguments ?? {});
|
|
69
|
+
return {
|
|
70
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
71
|
+
};
|
|
72
|
+
} catch (err) {
|
|
73
|
+
return {
|
|
74
|
+
isError: true,
|
|
75
|
+
content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const transport = new StdioServerTransport();
|
|
81
|
+
await server.connect(transport);
|
package/src/tools.js
CHANGED
|
@@ -63,6 +63,10 @@ export const TOOLS = [
|
|
|
63
63
|
} else {
|
|
64
64
|
setDefaultWorkspace(id);
|
|
65
65
|
}
|
|
66
|
+
// C6: surface the bearer-token admin URL as the most prominent line
|
|
67
|
+
// in the response. Operators paste it into their browser and land
|
|
68
|
+
// directly on the dashboard — no signup, no login, no OAuth.
|
|
69
|
+
const adminUrl = result.admin_url ?? null;
|
|
66
70
|
const payload = {
|
|
67
71
|
ok: true,
|
|
68
72
|
workspace: {
|
|
@@ -72,13 +76,23 @@ export const TOOLS = [
|
|
|
72
76
|
tier: ws.tier ?? "free",
|
|
73
77
|
created_at: ws.created_at,
|
|
74
78
|
},
|
|
79
|
+
// ⚡ Admin Dashboard (bookmark this!): the one URL operators need.
|
|
80
|
+
admin_url: adminUrl,
|
|
81
|
+
admin_url_expires_at: result.bearer_token_expires_at ?? null,
|
|
82
|
+
admin_url_message: adminUrl
|
|
83
|
+
? `⚡ 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
|
+
: null,
|
|
75
85
|
urls: result.urls ?? ws.urls ?? null,
|
|
86
|
+
public_urls: result.public_urls ?? null,
|
|
76
87
|
installed: result.installed ?? ["crm", "caldiy-booking", "formbricks-intake", "brain-v2"],
|
|
77
88
|
next: [
|
|
89
|
+
adminUrl
|
|
90
|
+
? `Open the admin dashboard: ${adminUrl} (paste into your browser; no signup needed)`
|
|
91
|
+
: null,
|
|
78
92
|
"install_vertical_pack({ pack: 'real-estate' }) // or 'dental', 'legal'",
|
|
79
93
|
"fetch_source_for_soul({ url: 'https://yoursite.com' }) → submit_soul({ soul })",
|
|
80
94
|
"get_workspace_snapshot({}) — read workspace state to reason about next steps",
|
|
81
|
-
],
|
|
95
|
+
].filter(Boolean),
|
|
82
96
|
};
|
|
83
97
|
return firstEver ? withFirstCallBanner(payload) : payload;
|
|
84
98
|
},
|
|
@@ -250,7 +264,7 @@ export const TOOLS = [
|
|
|
250
264
|
{
|
|
251
265
|
name: "update_landing_content",
|
|
252
266
|
description:
|
|
253
|
-
"Rewrite the workspace's public landing page
|
|
267
|
+
"Rewrite the workspace's public landing page hero — headline, subhead, and primary CTA label. C3.4 made this blueprint-aware: the operator's edit lands without losing any of the renderer's visual polish (typography, layered-shadow buttons, animations, etc.). Use this for the most common copy edits; for granular per-section / per-item edits use update_landing_section.",
|
|
254
268
|
inputSchema: obj(
|
|
255
269
|
{
|
|
256
270
|
headline: str("Main hero heading. Keep short; 1 line."),
|
|
@@ -258,7 +272,6 @@ export const TOOLS = [
|
|
|
258
272
|
cta_label: str("Primary call-to-action button text, e.g. 'Book a call'."),
|
|
259
273
|
workspace_id: str("Optional workspace override."),
|
|
260
274
|
},
|
|
261
|
-
["headline", "subhead", "cta_label"],
|
|
262
275
|
),
|
|
263
276
|
handler: async (a) => {
|
|
264
277
|
const ws = wsOrDefault(a.workspace_id);
|
|
@@ -273,6 +286,51 @@ export const TOOLS = [
|
|
|
273
286
|
});
|
|
274
287
|
},
|
|
275
288
|
},
|
|
289
|
+
{
|
|
290
|
+
name: "update_landing_section",
|
|
291
|
+
description:
|
|
292
|
+
"Granular per-field landing edit — change any single slot in any section of the blueprint-rendered landing page. Use when update_landing_content's three fields aren't enough. Section types: emergency-strip, hero, trust-strip, services-grid, about, mid-cta, testimonials, service-area, faq, footer. Field is a dot-segmented path on that section (e.g. 'headline', 'subhead', 'items.0.title', 'items.2.answer', 'showHours'). Value is the new value (string for copy, boolean for flags, etc.).",
|
|
293
|
+
inputSchema: obj(
|
|
294
|
+
{
|
|
295
|
+
section: {
|
|
296
|
+
type: "string",
|
|
297
|
+
enum: [
|
|
298
|
+
"emergency-strip",
|
|
299
|
+
"hero",
|
|
300
|
+
"trust-strip",
|
|
301
|
+
"services-grid",
|
|
302
|
+
"about",
|
|
303
|
+
"mid-cta",
|
|
304
|
+
"testimonials",
|
|
305
|
+
"service-area",
|
|
306
|
+
"faq",
|
|
307
|
+
"footer",
|
|
308
|
+
],
|
|
309
|
+
},
|
|
310
|
+
field: str(
|
|
311
|
+
"Dot-segmented field path on the section. Examples: 'headline', 'subhead', 'items.0.title', 'items.2.answer', 'showHours'."
|
|
312
|
+
),
|
|
313
|
+
value: {
|
|
314
|
+
description:
|
|
315
|
+
"New value for the field. String for copy, number for ratings, boolean for flags, object/array for richer slots.",
|
|
316
|
+
},
|
|
317
|
+
workspace_id: str("Optional workspace override."),
|
|
318
|
+
},
|
|
319
|
+
["section", "field", "value"],
|
|
320
|
+
),
|
|
321
|
+
handler: async (a) => {
|
|
322
|
+
const ws = wsOrDefault(a.workspace_id);
|
|
323
|
+
return api("POST", "/landing/section/update", {
|
|
324
|
+
body: {
|
|
325
|
+
section: a.section,
|
|
326
|
+
field: a.field,
|
|
327
|
+
value: a.value,
|
|
328
|
+
workspace_id: ws,
|
|
329
|
+
},
|
|
330
|
+
workspace_id: ws,
|
|
331
|
+
});
|
|
332
|
+
},
|
|
333
|
+
},
|
|
276
334
|
// Note: `customize_intake_form` used to live here pointing at POST
|
|
277
335
|
// /api/v1/intake/customize. Phase 2.d unified it under update_form; the
|
|
278
336
|
// alias that preserves the old name now lives in the Phase 2.d block at
|
package/src/welcome.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const VERSION = "1.0.
|
|
1
|
+
export const VERSION = "1.0.2";
|
|
2
2
|
|
|
3
3
|
export const WELCOME_MARKDOWN = `# SeldonFrame — your AI-native Business OS
|
|
4
4
|
|
|
@@ -16,15 +16,63 @@ intent and call the appropriate typed tool. The backend applies the change
|
|
|
16
16
|
deterministically. Zero backend LLM cost means the free tier is genuinely
|
|
17
17
|
free forever.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quick start — describe your business
|
|
22
|
+
|
|
23
|
+
To create a personalized workspace in a single turn, ask the user to paste
|
|
24
|
+
this template into Claude Code and fill in their details:
|
|
20
25
|
|
|
21
26
|
\`\`\`text
|
|
22
|
-
|
|
27
|
+
Create a workspace for my business:
|
|
28
|
+
- Business name: [your business name]
|
|
29
|
+
- Industry: [e.g., hvac, dental, legal, coaching, real-estate, salon, auto-repair]
|
|
30
|
+
- Location: [city, state/province]
|
|
31
|
+
- Operating hours: [e.g., Mon-Sat 7am-7pm]
|
|
32
|
+
- Team size: [number of people / trucks / stations]
|
|
33
|
+
- Services offered: [list your main services]
|
|
34
|
+
- Website: [URL, if you have one]
|
|
23
35
|
\`\`\`
|
|
24
36
|
|
|
25
|
-
|
|
37
|
+
When the user replies with that filled in, YOU should orchestrate the
|
|
38
|
+
following tool sequence (each call's response is structured — chain them):
|
|
39
|
+
|
|
40
|
+
1. \`create_workspace({ name: "<business name>", source: "<website if provided, else a 1-paragraph description>" })\`
|
|
41
|
+
— mints the hosted workspace + bearer token. The \`source\` arg seeds the Soul.
|
|
42
|
+
2. If \`industry\` is provided, call \`install_vertical_pack({ pack: "<industry-slug>" })\`
|
|
43
|
+
— adds domain-specific objects, fields, and views.
|
|
44
|
+
Built-in packs: \`real-estate-agency\`. For other industries, the backend
|
|
45
|
+
synthesizes a custom pack via \`/api/v1/verticals/generate\`. If a builtin
|
|
46
|
+
pack matches, prefer it; otherwise call generate first, then install.
|
|
47
|
+
3. If \`hours\` is provided, call \`configure_booking({ title, duration_minutes, description })\`
|
|
48
|
+
— sets the booking page defaults. Inline the parsed hours into \`description\`
|
|
49
|
+
(the booking schema doesn't take a per-day hours object yet).
|
|
50
|
+
4. If \`website\` was provided, the \`source\` URL passed to step 1 already
|
|
51
|
+
triggered a Soul fetch. Confirm via \`get_workspace_snapshot({})\` and
|
|
52
|
+
call \`submit_soul({ soul })\` if you can extract a richer structured Soul.
|
|
53
|
+
5. If \`services\` were listed, customize the intake form to capture
|
|
54
|
+
service-of-interest as a multi-select using \`customize_intake_form({ fields })\`.
|
|
55
|
+
|
|
56
|
+
Present the final result as a summary: live URLs (public + admin), what
|
|
57
|
+
was installed, and 2-3 next-best-action suggestions.
|
|
58
|
+
|
|
59
|
+
## If the user just says "create a workspace" without details
|
|
60
|
+
|
|
61
|
+
Ask these questions, one at a time, BEFORE calling \`create_workspace\`:
|
|
62
|
+
|
|
63
|
+
1. What's your business name?
|
|
64
|
+
2. What industry are you in? (suggest: hvac, dental, legal, coaching, real-estate, salon, auto-repair, consulting, fitness, other)
|
|
65
|
+
3. Where are you located? (city, state)
|
|
66
|
+
4. What are your operating hours?
|
|
67
|
+
5. What services do you offer? (3-5 main ones)
|
|
68
|
+
6. Do you have a website I can learn from? (optional)
|
|
69
|
+
|
|
70
|
+
Then run the orchestration above. Don't dump all six questions in one
|
|
71
|
+
message — ask conversationally so the user can think.
|
|
72
|
+
|
|
73
|
+
---
|
|
26
74
|
|
|
27
|
-
## How to customize a workspace
|
|
75
|
+
## How to customize a workspace later
|
|
28
76
|
|
|
29
77
|
1. Call \`get_workspace_snapshot({})\` to see current state, Soul, blocks, recent events.
|
|
30
78
|
2. Decide what to change based on the user's intent.
|
|
@@ -56,11 +104,15 @@ Soul compilation runs HERE, not on the backend:
|
|
|
56
104
|
|
|
57
105
|
## When you'll need \`SELDONFRAME_API_KEY\`
|
|
58
106
|
|
|
59
|
-
The first workspace is free forever.
|
|
107
|
+
The first workspace is free forever. Paid tiers (Starter $49/mo, Operator
|
|
108
|
+
$99/mo, Agency $149/mo) unlock additional workspaces, custom domains,
|
|
109
|
+
white-label, and advanced Brain capabilities. A key is required for:
|
|
60
110
|
|
|
61
111
|
- Adding a **second workspace**
|
|
62
112
|
- Connecting a **custom domain**
|
|
63
113
|
- Publishing, exporting agents, rotating org-scoped secrets
|
|
114
|
+
- Accessing the admin browser surface (\`/dashboard\`, \`/contacts\`, \`/deals\`)
|
|
115
|
+
after \`link_workspace_owner({})\`
|
|
64
116
|
|
|
65
117
|
Get one at <https://app.seldonframe.com/settings/api> and
|
|
66
118
|
\`export SELDONFRAME_API_KEY=sk-…\`. The MCP will pick it up on next restart.
|
|
@@ -69,12 +121,12 @@ Get one at <https://app.seldonframe.com/settings/api> and
|
|
|
69
121
|
|
|
70
122
|
Once a key is set, \`link_workspace_owner({})\` attaches the active
|
|
71
123
|
workspace to your real account. This unlocks the admin URLs
|
|
72
|
-
(dashboard
|
|
73
|
-
bearer token stays valid — no rotation needed.
|
|
124
|
+
(\`/dashboard\`, \`/contacts\`, \`/deals\`) for browser use after sign-in.
|
|
125
|
+
The MCP bearer token stays valid — no rotation needed.
|
|
74
126
|
|
|
75
127
|
---
|
|
76
128
|
|
|
77
|
-
**Docs:** <https://
|
|
129
|
+
**Docs:** <https://seldonframe.com/docs> · **Homepage:** <https://seldonframe.com> · **Pricing:** <https://seldonframe.com/#pricing>
|
|
78
130
|
`;
|
|
79
131
|
|
|
80
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.`;
|