@seldonframe/mcp 1.12.0 → 1.13.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 +2 -1
- package/src/tools.js +147 -0
- package/src/welcome.js +10 -3
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seldonframe/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
|
+
"description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.13.0: INTAKE FORM STRUCTURAL PRIMITIVES — five new tools mirror the v1.11 landing-structure pattern but for the intake form surface (linear, no nesting). Operators can now ask 'add a phone field', 'put email at the top', 'rename mobile to cellphone', 'remove the rating question' and the agent calls one atomic tool. WHY: pre-v1.13 the agent had to use persist_block(intake) which REPLACES the whole questions array — a bad fit for incremental edits. v1.13 lets the agent do single-field operations without touching the rest of the form. WHAT SHIPS: (1) get_intake_structure({workspace_id}) — returns title, description, indexed list of fields with type + label + required + 1-line preview. (2) add_intake_field({workspace_id, field, position?}) — append (default) or insert. ID uniqueness enforced. (3) move_intake_field({workspace_id, from_index, to_index}) — splice semantics (atomic). (4) delete_intake_field({workspace_id, index}) — refuses to leave 0 fields (public submit needs ≥1). (5) update_intake_field({workspace_id, index, patch}) — patch any subset of {id, type, label, helper, required, options, ratingScale, validation, showIf}; ID changes blocked from colliding. Persistence touches BOTH Blueprint.intake.questions AND intake_forms.fields (the public POST handler reads the latter); both updated atomically per mutation, formbricks-stack-v1 re-renders. (6) 23 new unit tests covering applyAddField (append / insert / id collision / out-of-range), applyMoveField, applyDeleteField (boundary indices / minimum-1 guard), applyUpdateField (patch / id-collision / type+options together / empty-patch reject), and deriveFieldPreview (label + type + required marker + option count + truncation). Test count: 2475 (was 2452). DESIGN: same thin-harness/fat-skill pattern. Server only validates + persists. Agent's LLM picks WHICH field by reading get_intake_structure previews. Antifragile: as LLMs improve at intent-mapping, output quality rises with zero harness changes. NO migrations, NO env vars, NO new deps. Backward compatible — existing persist_block(intake) still works for full-form replaces.",
|
|
4
5
|
"description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.12.0: COMPOSITE-TREE PRIMITIVES — operators can now manifest ANY landing-page section by description (comparison columns, pricing tiers, 'how it works' steps, stats rows, side-by-side image+text, custom CTAs) without server-side type-per-block work. The agent's LLM composes a tree from 12 low-level primitives; the server validates + renders. WHY: typed blocks (hero, services, about, faq, cta) require dedicated SKILL.md + Zod schema + renderer mapping per type — fine for high-stakes surfaces, too rigid for the long tail of operator requests. WHAT SHIPS: (1) New SectionComposite section type — holds a recursive CompositeNode tree. (2) 12 primitive node kinds: containers (section / row / col / card), content leaves (heading / text / image / list / button / stat), special (embed / divider / spacer). embed.ref ∈ {services, faq, testimonials, hours, phone} pulls workspace-data into the section without re-typing. (3) Zod schema + structural validators: tree depth ≤ 4, children-per-container caps (section ≤ 8, row ≤ 4, card ≤ 8, list ≤ 12), heading levels descend without skipping, all text fields trim + length-cap. (4) Theme-aware renderer (~3 KB shared CSS, emitted once per page). Mobile-responsive by default (rows stack on narrow viewports). All operator/LLM-supplied text HTML-escaped on emit (no XSS). External nav buttons get target=_blank rel=noopener. (5) Voice scanner — scans all text-bearing fields against soul.voice.avoidWords; returns warnings (not errors) for the agent to self-correct on retry. (6) 2 new MCP tools: add_composite_section({ workspace_id, tree, position? }) and update_composite_section({ workspace_id, index, tree }). (7) get_block_skill('composite') serves a hand-written SKILL.md with the primitive vocabulary, validation rules, voice guidance, and 7 worked patterns (COMPARISON, STATS, HOW-IT-WORKS, SIDE-BY-SIDE, CALL-CTA, FEATURES-GRID, EMBED-DRIVEN-SERVICES). (8) 45 new unit tests (27 validation + 18 render). DESIGN PRINCIPLES: thin harness (server only validates + renders; no LLM judgment), fat skill (ONE skill file with vocabulary + patterns; agent's LLM does composition), antifragile (as LLMs improve at composing trees, validation_warnings rate drops without harness changes), permissive-on-read/strict-on-write (old persisted trees keep rendering even if schema evolves; new writes must validate). Test count: 2452 (was 2407). Backward compatible. NO migrations, NO env vars, NO new deps. Existing typed blocks (hero/services/etc.) untouched. v1.13 candidate: form primitives (intake field add/move/delete/update). v1.14: composite trees on the customer portal surface with customer-data embed refs.",
|
|
5
6
|
"description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.11.0: STRUCTURAL PRIMITIVES — three new tools that cut landing-page restructuring time from minutes to seconds, with index-based addressing that handles duplicate section types. WHY: v1.10's reorder_landing_sections used type-based addressing ('hero', 'services-grid', 'faq', ...) which breaks when types repeat (smoke test 2026-05-05: a workspace had two 'services-grid' sections — one grid-3 services, one stats — and the reorder API correctly refused as ambiguous, but the agent had no way to disambiguate without fetching + PowerShell-parsing landing_pages.blueprintJson, which took 8+ minutes). WHAT SHIPS: (1) get_landing_structure({workspace_id}) — returns ordered sections with index (0..N-1) + type + 1-line preview ('Vancouver's Trusted HVAC Family — Same-Day Service' for hero, '3 services (grid-3)' vs 'stats — 4 numbers' for services-grid duplicates). Cheap one-DB-read. Replaces the v1.10 fetch-and-parse workflow. (2) move_section({workspace_id, from_index, to_index}) — atomic single-section move with splice semantics (section at from_index is removed, then inserted at to_index in the result). Handles duplicate types because index is unambiguous. Existing reorder_landing_sections stays as the bulk path for clean cases. (3) delete_section({workspace_id, index}) — atomic single-section remove. Refuses to leave 0 sections (minimum is 1). Use to clean up unintended duplicates from v2 generation artifacts. (4) 22 new unit tests covering applyMove (forward / backward / no-op / immutability / out-of-range), applyDelete (boundary indices / minimum-1-section guard / immutability), and derivePreview (each section type emits a useful 1-liner; services-grid grid-3 vs stats are distinguishable; long text truncates). DESIGN PRINCIPLES: thin harness (server only does mechanical reorder/delete + re-render; no LLM judgment), fat skill (agent's LLM picks WHICH section based on operator intent + preview text), antifragile (as LLMs improve at intent-mapping, output quality rises with zero harness changes). Test count: 2407 (was 2385). Time-to-restructure: 8 min → ~3 sec. NO migrations, NO env vars, NO new deps. Backward compatible. v1.12 candidate: add_section + new section primitives (comparison block, pricing-table, gallery) — separate larger work.",
|
|
6
7
|
"description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.10.1: HOTFIX — upload_workspace_image now accepts image_url + local_file_path, eliminating the agent-side base64 round-trip that v1.10.0 forced. WHY: v1.10.0's image_data_b64-only design made the agent base64-encode bytes into the JSON tool argument; the encoded string had to fit in the agent's tool-input token budget (~16 KB base64 ≈ ~12 KB raw). A 100 KB logo forced the agent into manual resize loops (verified 2026-05-05 smoke test: 7m 31s for one Cloudinary URL upload — 4 resize iterations before bytes fit). WHAT SHIPS: (1) image_url body field — SF backend fetches the URL directly via fetch + streamed read with running byte counter (5 MB cap, 5s timeout). SSRF guards: HTTPS-only, loopback / RFC1918 private / link-local IPv4 (incl. 169.254.169.254 cloud metadata) / IPv6 ::1 rejected. file_name + content_type auto-derived from URL path extension; falls back to response Content-Type header for extensionless URLs. (2) local_file_path MCP-client branch — reads the file in the npm-package process (which runs on the operator's machine, no agent token budget involved), base64-encodes for transport to the SF backend. file_name + content_type auto-derived from path extension. (3) image_data_b64 unchanged — kept as a backward-compatible fallback for callers that already have bytes in hand. (4) Tool descriptor explicitly ranks the three sources (image_url > local_file_path > image_data_b64) so the agent picks correctly. (5) 18 new unit tests covering URL validation (HTTPS-only enforcement, loopback / RFC1918 / link-local rejection, public IPv4 acceptance, malformed URL rejection), content-type derivation from URL extension (handles query strings), and file-name basename extraction. Test count: 2385 (was 2367). Time-to-upload-a-logo: ~7 min → ~3 sec (>99% reduction). NO migrations, NO new env vars, NO new dependencies. Backward compatible.",
|
package/src/tools.js
CHANGED
|
@@ -2889,6 +2889,153 @@ export const TOOLS = [
|
|
|
2889
2889
|
},
|
|
2890
2890
|
},
|
|
2891
2891
|
|
|
2892
|
+
// ─── v1.13.0 — intake-form structural primitives ────────────────────────
|
|
2893
|
+
//
|
|
2894
|
+
// Five atomic primitives for the intake-form surface, mirroring the
|
|
2895
|
+
// landing-structure pattern: read / add / move / delete / update.
|
|
2896
|
+
// Index-based addressing; ID uniqueness enforced on add/update.
|
|
2897
|
+
// Linear (no nesting), so simpler than composite trees.
|
|
2898
|
+
|
|
2899
|
+
{
|
|
2900
|
+
name: "get_intake_structure",
|
|
2901
|
+
description:
|
|
2902
|
+
"Read the workspace's intake form: title, description, and the indexed list of fields with type + label + required + 1-line preview. Use this BEFORE add_intake_field / move_intake_field / delete_intake_field / update_intake_field to find the right index. Cheap one-DB-read.",
|
|
2903
|
+
inputSchema: obj(
|
|
2904
|
+
{ workspace_id: str("Workspace id.") },
|
|
2905
|
+
["workspace_id"],
|
|
2906
|
+
),
|
|
2907
|
+
handler: async (args) => {
|
|
2908
|
+
const ws = args.workspace_id;
|
|
2909
|
+
const result = await api("GET", "/workspace/v2/intake/structure", {
|
|
2910
|
+
workspace_id: ws,
|
|
2911
|
+
});
|
|
2912
|
+
return result;
|
|
2913
|
+
},
|
|
2914
|
+
},
|
|
2915
|
+
|
|
2916
|
+
{
|
|
2917
|
+
name: "add_intake_field",
|
|
2918
|
+
description:
|
|
2919
|
+
"Add ONE field to the intake form. Field shape: { id, type, label, required?, helper?, options?, validation?, ratingScale? }. Types: text, textarea, email, phone, number, select, multi-select, rating, date. " +
|
|
2920
|
+
"ID must be unique within the form (server rejects duplicates — IDs are the bind key for answers). For select/multi-select pass an `options` array. " +
|
|
2921
|
+
"Use when the operator wants a new question on the intake form ('add a phone field', 'ask about budget', 'add a checkbox for newsletter signup'). For content edits to existing fields use update_intake_field. " +
|
|
2922
|
+
"Position is optional — defaults to appending at the end. Use get_intake_structure first if you want to insert between specific fields.",
|
|
2923
|
+
inputSchema: obj(
|
|
2924
|
+
{
|
|
2925
|
+
workspace_id: str("Workspace id."),
|
|
2926
|
+
field: {
|
|
2927
|
+
type: "object",
|
|
2928
|
+
description:
|
|
2929
|
+
"The new IntakeQuestion object: { id (kebab-case, unique), type, label, required?, helper?, options?, ratingScale?, validation? }.",
|
|
2930
|
+
additionalProperties: true,
|
|
2931
|
+
},
|
|
2932
|
+
position: {
|
|
2933
|
+
type: "integer",
|
|
2934
|
+
description:
|
|
2935
|
+
"Optional 0-based insert index. Default: append. Use get_intake_structure to find the right slot.",
|
|
2936
|
+
minimum: 0,
|
|
2937
|
+
},
|
|
2938
|
+
},
|
|
2939
|
+
["workspace_id", "field"],
|
|
2940
|
+
),
|
|
2941
|
+
handler: async (args) => {
|
|
2942
|
+
const ws = args.workspace_id;
|
|
2943
|
+
const result = await api("POST", "/workspace/v2/intake/fields", {
|
|
2944
|
+
body: {
|
|
2945
|
+
workspace_id: ws,
|
|
2946
|
+
op: "add",
|
|
2947
|
+
field: args.field,
|
|
2948
|
+
position: args.position,
|
|
2949
|
+
},
|
|
2950
|
+
workspace_id: ws,
|
|
2951
|
+
});
|
|
2952
|
+
return result;
|
|
2953
|
+
},
|
|
2954
|
+
},
|
|
2955
|
+
|
|
2956
|
+
{
|
|
2957
|
+
name: "move_intake_field",
|
|
2958
|
+
description:
|
|
2959
|
+
"Move ONE intake field to a new position. Splice semantics — field at from_index is removed, then inserted at to_index in the result. Use when the operator says 'put email at the top' or 'move phone above address'. " +
|
|
2960
|
+
"Run get_intake_structure first to find the indices.",
|
|
2961
|
+
inputSchema: obj(
|
|
2962
|
+
{
|
|
2963
|
+
workspace_id: str("Workspace id."),
|
|
2964
|
+
from_index: { type: "integer", description: "0-based source index.", minimum: 0 },
|
|
2965
|
+
to_index: { type: "integer", description: "0-based target index in the result.", minimum: 0 },
|
|
2966
|
+
},
|
|
2967
|
+
["workspace_id", "from_index", "to_index"],
|
|
2968
|
+
),
|
|
2969
|
+
handler: async (args) => {
|
|
2970
|
+
const ws = args.workspace_id;
|
|
2971
|
+
const result = await api("POST", "/workspace/v2/intake/fields", {
|
|
2972
|
+
body: {
|
|
2973
|
+
workspace_id: ws,
|
|
2974
|
+
op: "move",
|
|
2975
|
+
from_index: args.from_index,
|
|
2976
|
+
to_index: args.to_index,
|
|
2977
|
+
},
|
|
2978
|
+
workspace_id: ws,
|
|
2979
|
+
});
|
|
2980
|
+
return result;
|
|
2981
|
+
},
|
|
2982
|
+
},
|
|
2983
|
+
|
|
2984
|
+
{
|
|
2985
|
+
name: "delete_intake_field",
|
|
2986
|
+
description:
|
|
2987
|
+
"Remove ONE intake field. Refuses to leave 0 fields (the public submit becomes meaningless without any inputs — minimum is 1). Use when the operator wants to remove a question from the form ('drop the property type field', 'remove the rating question'). For content edits use update_intake_field. Run get_intake_structure first to find the right index.",
|
|
2988
|
+
inputSchema: obj(
|
|
2989
|
+
{
|
|
2990
|
+
workspace_id: str("Workspace id."),
|
|
2991
|
+
index: { type: "integer", description: "0-based index of the field to delete.", minimum: 0 },
|
|
2992
|
+
},
|
|
2993
|
+
["workspace_id", "index"],
|
|
2994
|
+
),
|
|
2995
|
+
handler: async (args) => {
|
|
2996
|
+
const ws = args.workspace_id;
|
|
2997
|
+
const result = await api("POST", "/workspace/v2/intake/fields", {
|
|
2998
|
+
body: { workspace_id: ws, op: "delete", index: args.index },
|
|
2999
|
+
workspace_id: ws,
|
|
3000
|
+
});
|
|
3001
|
+
return result;
|
|
3002
|
+
},
|
|
3003
|
+
},
|
|
3004
|
+
|
|
3005
|
+
{
|
|
3006
|
+
name: "update_intake_field",
|
|
3007
|
+
description:
|
|
3008
|
+
"Patch ONE intake field by index. Patch can include any subset of: id, type, label, helper, required, options, ratingScale, validation, showIf. Only the fields you pass are changed; everything else stays. " +
|
|
3009
|
+
"Use for content edits ('rename phone to mobile', 'make email optional', 'add a fourth option to property type', 'change the helper text'). " +
|
|
3010
|
+
"ID changes must not collide with another field's id (server rejects). For structural changes (add/remove fields) use the dedicated tools.",
|
|
3011
|
+
inputSchema: obj(
|
|
3012
|
+
{
|
|
3013
|
+
workspace_id: str("Workspace id."),
|
|
3014
|
+
index: { type: "integer", description: "0-based index of the field to patch.", minimum: 0 },
|
|
3015
|
+
patch: {
|
|
3016
|
+
type: "object",
|
|
3017
|
+
description:
|
|
3018
|
+
"Subset of IntakeQuestion fields to overwrite. Empty patch is rejected. ID changes are allowed but must not collide with another field.",
|
|
3019
|
+
additionalProperties: true,
|
|
3020
|
+
},
|
|
3021
|
+
},
|
|
3022
|
+
["workspace_id", "index", "patch"],
|
|
3023
|
+
),
|
|
3024
|
+
handler: async (args) => {
|
|
3025
|
+
const ws = args.workspace_id;
|
|
3026
|
+
const result = await api("POST", "/workspace/v2/intake/fields", {
|
|
3027
|
+
body: {
|
|
3028
|
+
workspace_id: ws,
|
|
3029
|
+
op: "update",
|
|
3030
|
+
index: args.index,
|
|
3031
|
+
patch: args.patch,
|
|
3032
|
+
},
|
|
3033
|
+
workspace_id: ws,
|
|
3034
|
+
});
|
|
3035
|
+
return result;
|
|
3036
|
+
},
|
|
3037
|
+
},
|
|
3038
|
+
|
|
2892
3039
|
{
|
|
2893
3040
|
name: "reorder_landing_sections",
|
|
2894
3041
|
description:
|
package/src/welcome.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// stripped. `create_full_workspace` is the only workspace-creation
|
|
9
9
|
// path mentioned anywhere in this briefing.
|
|
10
10
|
|
|
11
|
-
export const VERSION = "1.
|
|
11
|
+
export const VERSION = "1.13.0";
|
|
12
12
|
|
|
13
13
|
export const WELCOME_MARKDOWN = `# SeldonFrame — create a real Business OS in one conversation
|
|
14
14
|
|
|
@@ -246,7 +246,14 @@ further natural-language requests ("change the headline to …",
|
|
|
246
246
|
- **\`update_landing_content\`** / **\`update_landing_section\`** —
|
|
247
247
|
edit the website's headline, subhead, sections, copy.
|
|
248
248
|
- **\`update_theme\`** — change colors, fonts, dark/light mode.
|
|
249
|
-
- **\`
|
|
249
|
+
- **\`get_intake_structure\`** / **\`add_intake_field\`** /
|
|
250
|
+
**\`move_intake_field\`** / **\`delete_intake_field\`** /
|
|
251
|
+
**\`update_intake_field\`** (v1.13+) — atomic primitives for
|
|
252
|
+
editing the intake form one field at a time. Index-based, ID
|
|
253
|
+
uniqueness enforced. Use these for incremental edits ("add a
|
|
254
|
+
phone field", "rename email to primary email"). For full-form
|
|
255
|
+
replaces use persist_block(intake).
|
|
256
|
+
- **\`update_form\`** — edit the intake form's questions (legacy).
|
|
250
257
|
- **\`update_appointment_type\`** — edit the booking page's slot length,
|
|
251
258
|
title, description.
|
|
252
259
|
- **\`install_vertical_pack\`** — set up an industry template
|
|
@@ -280,4 +287,4 @@ admin dashboard. Pre-fills their email automatically.
|
|
|
280
287
|
<https://seldonframe.com> · **Discord:** <https://discord.gg/sbVUu976NW>
|
|
281
288
|
`;
|
|
282
289
|
|
|
283
|
-
export const FIRST_CALL_BANNER = `🚀 SeldonFrame v1.
|
|
290
|
+
export const FIRST_CALL_BANNER = `🚀 SeldonFrame v1.13.0 is connected. PREFERRED workspace creation: create_workspace_v2 → IN PARALLEL for all 7 recommended_blocks (hero, services, about, faq, cta, booking, intake): get_block_skill + persist_block → complete_workspace_v2 → finalize_workspace({ workspace_id, email }). The v2 flow puts YOUR LLM in charge of every operator-facing surface using one SKILL.md per block. Each block's prop schema is server-validated. Run blocks in PARALLEL (Promise.all) — sequential takes 60+ seconds. v1.10+ TIER 2 CUSTOMIZE TOOLS: regenerate_block (re-do one block with operator instructions; thin-harness — server bundles context, your LLM generates), upload_workspace_image (set logo/hero_background; v1.10.1+ accepts image_url or local_file_path — DON'T base64 unless you have to, the encoded string eats your tool-call token budget). v1.11+ STRUCTURAL PRIMITIVES: get_landing_structure → move_section → delete_section. INDEX-based, handle duplicate section types, atomic. v1.12+ COMPOSITE TREES: add_composite_section / update_composite_section — manifest ANY block (comparison, pricing, "how it works," stats, custom CTAs) from 12 low-level primitives. Server validates + renders; YOUR LLM composes. Read the SKILL.md via get_block_skill('composite') before composing. Every URL is real. NEVER create local files. Skipping finalize_workspace leaves the operator with no admin login.`;
|