SeldonFrame
About
SeldonFrame is an open-source, MCP-native Business OS. One conversation in Claude Code or Cursor creates a live workspace — website, booking page, intake form, CRM, and an AI receptionist — on a real subdomain in about 3 minutes.
Details
- Author
- seldonframe
- Downloads
- 329
- Categories
- AI
Jump to
- Open-source and MCP-native architecture
- Creates a complete business workspace from one conversation
- Generates a website, booking page, and intake form
- Includes a built-in CRM
- Features an AI receptionist
- Deploys on a real subdomain in ~3 minutes
Setting up with Highlight
This MCP is not yet compatible with Highlight’s one-click setup. However, you can still use it with Highlight by following these steps:
- Download and install Highlight from highlightai.com/download
- Navigate to the plugins tab and select "Add Custom Plugin"
-
Configure the plugin with the settings below
Plugin Name
SeldonFrameCommand (node, npx, python, etc.)Please refer to the README for specific instructions on how to obtain API keys or other required environment variables.
- Enable "Start Automatically" if you want the plugin to start when Highlight launches
create_full_workspace
Atomic workspace creation from pre-extracted business facts. Equivalent to create_workspace_v2 but single-call (no block-by-block iteration). Used as the follow-up after `create_workspace_from_url` for cases where the operator wants the workspace produced in one shot rather than block-iterated. 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'] }). MANDATORY FOLLOW-UP: same as create_workspace_v2 — after this returns `status: 'ready'`, ask 'What email should I use for your account?' and call finalize_workspace({ workspace_id, email }).
create_workspace_from_google_paste
PREFERRED when the operator pastes a Google Maps business listing. Same atomic pipeline as create_full_workspace; this tool's docs guide the paste-to-fields extraction. Claude Code parses the paste BEFORE calling this tool — never pass the raw paste text. EXTRACTION RULES (apply in order): 1) business_name → the bold business title at the top of the listing. 2) phone → the digits next to the phone icon. 3) address → the line next to the location pin. Parse city + state from this address into separate fields (city/state). 4) services → derive from BOTH the categories chip row (e.g. 'Plumber · Emergency plumbing service') AND any explicit 'Services' section ('Drain cleaning', 'Water heater repair'). Dedupe; keep 5-12 distinct strings. 5) business_description → synthesize 1-2 sentences from the categories + 'About' / 'From the business' section. Include industry words verbatim (the personality classifier reads this). 6) review_rating + review_count → the '4.7 ★ (950)' element. 7) trust_signals → 'Licensed', 'Bonded', 'Insured', 'Family-owned' if mentioned. 8) emergency_service / same_day → set true if 'open 24 hours', '24/7', 'same-day service' appears. 9) service_area → cities mentioned in 'Service area' section. 10) weekly_hours → parse the hours block ('Monday: 9 AM-5 PM, Tuesday: closed, ...') into the canonical shape: {monday:{enabled:true,start:'09:00',end:'17:00'},tuesday:{enabled:false,start:'09:00',end:'17:00'},...}. Keys MUST be FULL DAY NAMES (sunday/monday/.../saturday); times MUST be HH:MM 24-hour. 'Closed' → enabled:false (start/end are placeholders). 'Open 24 hours' → start:'00:00', end:'23:59'. These hours are written DIRECTLY to the booking template's availability — wrong shape = booking page falls back to Mon-Fri 9-5 default. 11) google_place_url → the Maps URL the operator pasted, if visible. Optional, stored on soul.business.maps_url for audit. MANDATORY FOLLOW-UP: same as create_full_workspace — after this returns `status: 'ready'`, ask 'What email should I use for your account?' and call finalize_workspace({ workspace_id, email }).
create_workspace_from_url
Entry point for URL-based workspace creation. Returns instructions Claude follows: WebFetch the URL, optionally WebFetch up to 2 priority sub-pages (e.g. /about, /services, /contact, /pricing), extract structured business facts, ask the operator for any required field that can't be determined, then call `create_full_workspace` with the result — ONE atomic call that builds the workspace + the production multi-page website (vertical-aware landing + per-service detail pages, the same engine as the SeldonFrame dashboard) + booking page + intake form + CRM + draft chatbot. THIS TOOL DOES NOT CREATE A WORKSPACE — it returns the playbook. The workspace is created by the follow-up `create_full_workspace` call. Do NOT use the block-iterated create_workspace_v2 flow for URL builds. When operator provides ANY of: 'http://', 'https://', a domain (.com/.io/.net/.co/.app/.dev/.us etc), or 'create workspace for <URL>' — call this tool. Do NOT manually WebFetch first; the instructions returned by this tool tell you exactly what to fetch and in what order. MANDATORY FOLLOW-UP: After create_full_workspace returns, ask the operator 'What email should I use for your account?' and call finalize_workspace({ workspace_id, email }).
generate_landing_page
Generate a SeldonFrame-hosted landing page for an EXISTING workspace. USE-WHEN: operator explicitly asks for a landing page after the workspace exists, OR the client has no website of their own and the agency wants SeldonFrame to host the public-facing site. If the client already has a website (the common agency case), the chatbot embed snippet returned by create_workspace_from_url is the canonical deliverable; you do NOT need a generated landing page. Latency: ~30-60s. Returns the public landing URL. Operator can later customize per-block via update_landing_section / persist_block.
list_workspaces
List all workspaces known to this device (plus any Pro workspaces if SELDONFRAME_API_KEY is set).
switch_workspace
Set the active workspace. Subsequent tool calls act on it by default.
clone_workspace
Clone an existing workspace as a template. Example: clone_workspace({ source_workspace_id: 'wsp_x', name: 'Copy' })
link_workspace_owner
Claim an anonymously-created workspace under your real account. After linking, the admin URLs (dashboard, contacts, deals) become usable once you sign in at app.seldonframe.com. Requires SELDONFRAME_API_KEY to be set in the MCP environment. The workspace bearer token continues to work — no rotation needed. Example: link_workspace_owner({}) to claim the active workspace.
send_welcome_email
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. If the workspace has an AI chatbot (create_full_workspace's response includes chatbot_embed_snippet + chatbot_agent_id), pass chatbot_url + chatbot_embed_snippet + chatbot_status so the welcome email also shows the chatbot card with its embed snippet — operators otherwise never learn the workspace shipped with a chatbot. All three are optional; omit them to send the classic 4-URL email. Example: send_welcome_email({ email: 'alice@example.com', name: 'Alice', chatbot_url: 'https://acme.app.seldonframe.com/chat', chatbot_embed_snippet: '<script src="..." async></script>', chatbot_status: 'test' }).
collect_operator_email
MANDATORY post-workspace step. Send the operator's welcome email + record them as a lead in SeldonFrame's CRM. EVERY workspace creation flow MUST end with this call — it is the only thing that triggers the welcome email (with the live URLs), creates their account so the admin login keeps working past the 7-day token, captures them in our pipeline so we can follow up, and lets Stripe pre-fill their email on upgrade. Skipping it leaves the operator with a one-shot URL and no way to recover access. Flow: after create_workspace returns, ask the user verbatim 'What email should I use for your account? This is where you'll get your login link and any notifications.' Then call this tool with whatever email they give you. If you'd rather use the wrapper, call `finalize_workspace({ email })` instead — same effect plus a formatted summary at the end. Example: collect_operator_email({ email: 'max@precisionplumbing.com', name: 'Max' })
finalize_workspace
ONE-CALL CLOSING WRAPPER for the workspace creation flow. Bundles email collection (welcome email + lead capture via collect_operator_email) AND produces the final operator-facing summary (live URLs, what's configured, admin link). Call this as the LAST step of every workspace creation. After create_workspace returns, ask the user 'What email should I use for your account? This is where you'll get your login link and any notifications.' Then call this tool with the email they give you. Returns a `summary` string Claude Code should paraphrase verbatim to the operator. Use this instead of calling collect_operator_email directly when you want a single tool call to close the loop. Skipping this is the same as skipping email collection — leaves the operator with a one-shot URL and no recovery path. Example: finalize_workspace({ email: 'max@precisionplumbing.com', name: 'Max' }). The summary is agency-voice: addresses the operator AS an agency delivering for their SMB client, not as the workspace owner. When relaying to the operator, preserve the 'your client' framing throughout — don't rewrite to 'your workspace'.
revoke_bearer
Revoke workspace bearer tokens. Useful if a device token has leaked or if a builder wants to rotate. Modes (pick exactly one): `{}` revokes ALL tokens except the current device's (safe default — other devices kicked off, this device keeps working); `{ token_id }` revokes a specific token by its UUID; `{ all: true }` revokes every token including the current one — requires SELDONFRAME_API_KEY because it locks this device out. After revoking the current token the MCP clears the local entry from ~/.seldonframe/device.json.
update_landing_content
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.
update_landing_section
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.).
update_theme
Change workspace theme: mode (dark|light), primary_color (#hex), accent_color (#hex), font_family. Any subset. Available fonts: Inter, DM Sans, Playfair Display, Space Grotesk, Lora, Outfit.
apply_design_md
Apply a DESIGN.md file (the Google Labs format: YAML front matter for tokens + Markdown for rationale) to the workspace theme. Maps tokens.colors.primary, tokens.colors.accent, tokens.mode, and tokens.typography.body to OrgTheme fields. Unmapped tokens (spacing, custom shadows, etc.) are returned so Claude Code can decide whether to apply them via update_landing_page or surface to the operator. USE-WHEN the operator says: 'apply my DESIGN.md', 'use this brand kit', 'match my company's design tokens', 'import my design system', or 'theme my workspace from this file'. Example: apply_design_md({ design_md_content: '<full file content as string>' })
apply_motion_preset
Set the workspace's motion intensity preset. Stored on OrgTheme.motionPreset and read by the renderer + Claude Code as a hint for content generation. Presets: 'minimal' (no motion, accessibility-first), 'subtle' (fade-up reveals only), 'balanced' (reveals + stagger + hover-lift — the default), 'editorial' (full effects: counters, magnetic CTAs, text-reveal). USE-WHEN the operator says: 'make my pages feel more premium', 'tone down the animation', 'I want it editorial', 'respect reduced motion', 'no animation please', or directly references a preset name. Example: apply_motion_preset({ preset: 'editorial' })
import_claude_design_handoff
Validate a Claude Design handoff bundle (the artifact Claude Design produces when designs are ready for code), apply its embedded design tokens to the workspace theme, and return a structured manifest of the components with per-component next-step instructions for wiring them into pages. Does NOT auto-execute generated React on live pages — components route through human/eval review (the same gate that protects published agents) before customer-facing surfaces ship. USE-WHEN the operator says: 'import this Claude Design handoff', 'wire up these components', 'I just exported a design from Claude Design', or 'apply this design bundle to my workspace'. Example: import_claude_design_handoff({ bundle: { meta: { project_name: 'Acme HVAC' }, tokens: { colors: { primary: '#0e7490' } }, components: [{ name: 'TrustStrip', surface: 'landing', react_source: '<TSX content>' }] } })
list_automations
List automations configured in the active (or specified) workspace.
install_caldiy_booking
Install the booking page (event types, availability, scheduled bookings). Example: install_caldiy_booking({})
install_formbricks_intake
Install an intake form (questions, conditional logic, automatic CRM sync). Example: install_formbricks_intake({})
get_workspace_snapshot
Return a structured read-only snapshot of workspace state: workspace metadata, Soul (if submitted), theme, enabled blocks with configs, entity counts (contacts/bookings/intake forms/submissions), recent Seldon It events, and public URLs. YOU reason over this snapshot to decide what to do next, then call the appropriate typed tools (update_landing_content, configure_booking, customize_intake_form, update_theme, install_*). Zero server-side LLM cost.
fetch_source_for_soul
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. v1.59.2 — only public http(s) URLs are fetched: the MCP server rejects localhost / private-network / link-local targets (and any redirect that lands on one) before making the request. Use this for the operator's real, public-facing website only.
submit_soul
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.
connect_custom_domain
Connect + verify a custom domain. Pro capability — requires SELDONFRAME_API_KEY. Example: connect_custom_domain({ domain: 'app.mysite.com' })
export_agent
Export the current workspace as a portable .agent/ bundle.
store_secret
Store a workspace-scoped secret (encrypted at rest). Example: store_secret({ key: 'STRIPE_API_KEY', value: 'sk_…' })
list_secrets
List secret metadata (names, timestamps) without exposing plaintext.
rotate_secret
Rotate or delete a workspace secret. Omit new_value to delete.
list_contacts
List contacts in the active workspace. Returns every contact the caller can read. Example: list_contacts({}).
get_contact
Fetch one contact by id. Example: get_contact({ contact_id: 'abc-...' }).
create_contact
Create a new contact. Typical use: 'Add Jane Doe jane@acme.co as a lead'. Example: create_contact({ first_name: 'Jane', last_name: 'Doe', email: 'jane@acme.co', status: 'lead' }).
update_contact
Update fields on an existing contact. Partial — omit fields you don't want to change. Example: update_contact({ contact_id: '...', status: 'customer' }).
delete_contact
Delete a contact and all linked deals/activities (cascades via FK). Irreversible. Example: delete_contact({ contact_id: '...' }).
list_deals
List deals in the active workspace. Example: list_deals({}).
get_deal
Fetch one deal by id. Example: get_deal({ deal_id: '...' }).
create_deal
Create a new deal attached to a contact on the default pipeline. Typical use: 'Create a $5k deal for Jane Doe at the Discovery stage'. Example: create_deal({ contact_id: '...', title: 'Q2 retainer', value: 5000, stage: 'Discovery' }).
update_deal
Update a deal. Partial — omit fields to keep them. For stage-only moves prefer move_deal_stage (clearer intent). Example: update_deal({ deal_id: '...', value: 7500 }).
move_deal_stage
Move a deal to a new stage. Same effect as dragging the card on the kanban. Example: move_deal_stage({ deal_id: '...', to_stage: 'Proposal' }).
delete_deal
Delete a deal. Irreversible. Example: delete_deal({ deal_id: '...' }).
list_activities
List activity log entries (tasks, notes, email sent, booking created, etc.) across the workspace. Example: list_activities({}).
create_activity
Append an activity-log entry to a contact (and/or deal). Use this instead of stuffing agent reminders into contacts.notes — notes gets overwritten on updates; activities are append-only. Valid types: task, note, email, sms, call, meeting, stage_change, payment, review_request, agent_action. Example: create_activity({ contact_id: 'ctc_...', type: 'agent_action', subject: 'Speed-to-Lead agent booked consult', body: 'Scheduled for 2026-05-01' })
list_bookings
List scheduled bookings (not appointment-type templates — see list_appointment_types for those). Supports filtering by contact, status, and date range. Default sort: most-recent-first; if `from` is set, switches to earliest-upcoming-first for reminder flows. Example: list_bookings({ from: '2026-04-22T00:00:00Z', limit: 20 })
create_coupon
Create a Stripe coupon + matching per-contact redeemable promotion code on the workspace's connected Stripe account. Use for Win-Back / retention agents that need UNIQUE codes per recipient (shared codes are vulnerable to abuse + lose attribution signal). Default max_redemptions=1 + auto-generated code string. Requires the workspace to have completed Stripe Connect onboarding. Example: create_coupon({ percent_off: 20, duration: 'once', name: 'Win-Back 20% off' })
create_booking
Schedule a real booking against an existing appointment type. Looks up the template by id, creates a scheduled row on the workspace calendar, stamps the contact's name + email, emits booking.created, and — if the appointment type has a price > 0 — returns a Stripe Checkout URL routed to the SMB's connected Stripe account so the builder / agent can text or email the payment link to the contact. Example: create_booking({ contact_id: 'ctc_...', appointment_type_id: 'appt_...', starts_at: '2026-05-01T15:00:00Z' })
get_booking
Fetch one scheduled booking by id. Returns the full detail (contact, times, status, notes, meeting URL, cancellation timestamp, metadata). Appointment-type templates are NOT returned here — use list_appointment_types for those. 404s if the id is unknown OR belongs to a different workspace. Example: get_booking({ booking_id: 'bkg_...' }).
cancel_booking
Cancel a scheduled booking. Sets status to 'cancelled', stamps cancelledAt, deletes the Google Calendar event, and emits booking.cancelled. Idempotent — re-cancelling an already-cancelled booking is a 200 no-op with alreadyCancelled=true (no duplicate events, no calendar errors). Past-dated bookings CAN be cancelled (legitimate retroactive cleanup). Does NOT touch linked payments — linkedPaymentIds is returned so the agent can compose refund_payment explicitly if the business rule is 'cancel AND refund'. Example: cancel_booking({ booking_id: 'bkg_...' }).
reschedule_booking
Move a scheduled booking to a new starts_at. Preserves the original duration — endsAt tracks the move so a 30-min consult stays 30 mins at the new time. Updates the Google Calendar event in place (event id preserved; attendees see the time change on their existing invite) and emits booking.rescheduled with both previousStartsAt and newStartsAt so follow-up agents can describe the change. Rejects past-dated new starts_at (400) and refuses to reschedule a cancelled booking (422 — reviving a cancellation should be a new create_booking). Does NOT change appointment type; does NOT touch linked payments. Example: reschedule_booking({ booking_id: 'bkg_...', starts_at: '2026-05-02T15:00:00Z' }).
list_appointment_types
List all appointment types (bookable templates) in the workspace. Example: list_appointment_types({}).
create_appointment_type
Create a new appointment type with its own public /book/<slug> URL. Defaults availability to Mon–Fri 9am–5pm (edit on /bookings to change). Example: create_appointment_type({ title: 'Strategy call', duration_minutes: 45, price: 150 }).
update_appointment_type
Update an existing appointment type. Partial — omit fields to keep them. Example: update_appointment_type({ booking_slug: 'default', duration_minutes: 60, price: 200 }). Pass booking_slug='default' to edit the auto-seeded 'Book a call' template.
configure_booking
DEPRECATED alias for update_appointment_type({ booking_slug: 'default', ... }). Kept so existing Claude Code sessions don't break. Prefer update_appointment_type for new scripts.
list_forms
List intake forms in the workspace. Example: list_forms({}).
get_form
Fetch one form by id or slug. Example: get_form({ form: 'contact' }) or get_form({ form: 'uuid…' }).
create_form
Create a new intake form. Pass template_id to pre-fill fields from a built-in template (contact, lead-qualification, booking-request, nps-feedback, event-registration, blank). Example: create_form({ template_id: 'contact' }) → uses 'Contact us' template. Or pass explicit fields: create_form({ name: 'Intake', fields: [{ key: 'email', label: 'Email', type: 'email', required: true }] }).
update_form
Update a form. Partial — omit fields to keep them. Replacing `fields` replaces the whole array (each field: { key, label, type, required, options? }). Example: update_form({ form: 'intake', fields: [...] }).
delete_form
Delete a form. Irreversible. Submissions are NOT deleted (form_submissions has ON DELETE SET NULL on form_id). Example: delete_form({ form: 'old-survey' }).
list_submissions
List submissions for a form. Example: list_submissions({ form_id: 'uuid…' }).
customize_intake_form
DEPRECATED alias for update_form({ form: 'intake', fields }). Only edits the auto-seeded default form; prefer update_form for new scripts so you can target any form in the workspace.
send_email
Send a one-off email through the workspace's configured provider (Resend by default). Checks the suppression list before sending and skips with {suppressed: true} if the recipient has opted out. Example: send_email({ to: 'alex@acme.com', subject: 'Welcome', body: 'Thanks for signing up', contact_id: 'ctc_123' })
list_emails
List recent emails sent from the workspace, newest first. Useful for checking delivery status before following up.
get_email
Fetch a single email with its full provider-event history (sent / delivered / opened / clicked / bounced).
list_suppressions
List all suppressed email addresses for the workspace — who is opted out and why (manual / unsubscribe / bounce / complaint).
suppress_email
Add an email address to the workspace suppression list so future sends skip it. Use for manual unsubscribes or policy blocks.
unsuppress_email
Remove an email address from the workspace suppression list so future sends go through again.
send_sms
Send an SMS via the workspace's Twilio integration. Checks the SMS suppression list first (STOP keyword + carrier blocks + manual opt-outs) and skips with {suppressed: true} if the recipient has opted out. Example: send_sms({ to: '+15551234567', body: 'Your appointment is confirmed for Tuesday 2pm', contact_id: 'ctc_123' })
list_sms
List recent SMS messages (inbound + outbound) for the workspace, newest first.
get_sms
Fetch a single SMS with its full provider-event history (queued / sent / delivered / failed / undelivered).
list_sms_suppressions
List all suppressed phone numbers for the workspace — who is opted out and why (manual / stop_keyword / carrier_block / complaint).
suppress_phone
Add a phone number to the SMS suppression list so future SMS sends skip it. STOP replies + carrier permanent-failure codes auto-suppress via the Twilio webhook; use this for manual opt-outs.
unsuppress_phone
Remove a phone number from the SMS suppression list so future sends go through again.
create_invoice
Draft a Stripe invoice on the workspace's connected Stripe account. Invoice is created but not sent — call send_invoice separately so agents can review before dispatch. Contact must have an email. Example: create_invoice({ contact_id: 'ctc_123', items: [{ description: '1 hr consulting', quantity: 1, unit_amount: 200 }], due_at: '2026-05-21T00:00:00Z' })
list_invoices
List workspace invoices (draft + sent + paid + past_due + voided), newest first.
get_invoice
Fetch an invoice + its line items + hosted invoice URL (for payment).
send_invoice
Dispatch a draft invoice to the contact via Stripe (Stripe emails the invoice + provides a hosted pay page).
void_invoice
Void an invoice (undo a billing error). Only valid for draft / open invoices; paid invoices must be refunded instead.
create_subscription
Start a recurring subscription for a contact against a Stripe Price id. The Price must already exist in the workspace's Stripe dashboard — v1 does not create Prices. Example: create_subscription({ contact_id: 'ctc_123', price_id: 'price_1ABCxyz', trial_days: 14 })
list_subscriptions
List workspace subscriptions (active + trialing + past_due + canceled), newest first.
cancel_subscription
Cancel a subscription. Default: cancel at period end (contact keeps access until renewal date). Pass immediate=true for an instant termination + prorated refund.
list_payments
List recent payments (completed + failed + refunded + disputed) across the workspace, newest first.
get_payment
Fetch a single payment record with status + refund/dispute state.
refund_payment
Refund a payment. Omit amount to refund the full payment; pass amount for a partial refund. reason should be 'duplicate' | 'fraudulent' | 'requested_by_customer'.
list_landing_pages
List the workspace's landing pages (draft + published), newest-updated first.
get_landing_page
Fetch a single landing page with its full Puck payload + metadata.
update_landing_page
Update a landing page's title and/or Puck payload. Validates puck_data on the way through. Does not change publish status — use publish_landing_page for that.
publish_landing_page
Flip a landing page between draft and published. Publishing busts the public-URL cache immediately and emits landing.published. Pass published=false to unpublish.
list_landing_templates
List the pre-built vertical landing-page templates. Each has a validated Puck payload ready to seed a new page via create_landing_page({puck_data: template.payload}).
get_landing_template
Fetch a single landing-page template including its Puck payload. Pair with create_landing_page to seed a new page from the template.
generate_landing_page
Generate a Puck landing-page payload from a natural-language prompt using Claude + the workspace's Soul + theme. Returns the payload (validated against the Puck schema) but does NOT persist — pair with create_landing_page to save the result. Example: generate_landing_page({ prompt: 'A landing for a Laval dental clinic, focus on new-patient consultations' })
send_conversation_turn
Route an incoming message through the Conversation Primitive runtime. Loads prior turns for (contact, channel), generates a Soul-aware reply with Claude, writes both inbound + outbound turns, and emits conversation.turn.received / sent events. Use when building an always-on conversational agent (speed-to-lead, qualification chatbot). Example: send_conversation_turn({ contact_id: 'ctc_123', channel: 'sms', message: 'Do you have Saturday appointments?' })
create_workspace_v2
Create a workspace from pre-extracted business facts. Used as the follow-up call after `create_workspace_from_url` returns extraction instructions, OR directly when the operator provides structured info (no URL). Flow: 1) call this tool with the business facts; 2) for each block in `v2.recommended_blocks`, call get_block_skill(name) and use your LLM to generate props matching the SKILL.md prompt + schema; 3) call persist_block({ workspace_id, block_name, generation_prompt, props }) for each; 4) call complete_workspace_v2({ workspace_id }). MANDATORY FOLLOW-UP: After this returns `status: 'ready'` AND after all blocks land via persist_block + complete_workspace_v2, ask the operator verbatim 'What email should I use for your account?' Then call finalize_workspace({ workspace_id, email }). The admin dashboard URL is created by finalize_workspace, not here.
list_blocks
List all v2 page-block primitives available in this SF backend. Returns each block's name, version, section type, one-line description, and the URL where its full SKILL.md (the generation prompt + prop schema) lives. Use this when you need to discover what blocks exist; for actual block content use get_block_skill.
get_block_skill
Fetch the SKILL.md (the full generation prompt + prop schema + voice rules + worked examples + validator definitions) for one v2 page block. Returns raw markdown text. Read it carefully BEFORE generating props — the prop schema in the YAML frontmatter is enforced by the persist_block endpoint, and the validators run on every save. Generation that ignores the SKILL.md will fail validation and the operator will see worse output.
persist_block
Persist a v2 block instance. Call this after you've read the block's SKILL.md and generated props matching its schema. The server validates props (Zod schema + deterministic copy-quality validators), renders the block via the existing renderer, replaces the matching section in the workspace's landing page, and returns the public URL where the change is now visible. For initial generation, omit `customization`. For operator-driven edits ('make the hero warmer', 'add a card about kids cuts'), pass `customization: { prompt }` — the operator's prompt is appended to the row's customization history (forever-frozen rule), and the new props replace the previous render. Returns `validation_errors` on failure — if you see them, regenerate the props with the rules from SKILL.md applied more carefully and retry. Don't show validation errors to the operator; they're for you.
complete_workspace_v2
Mark the v2 flow finished for a workspace. Returns which blocks landed vs. were skipped (skipped ones still render via the v1 default pipeline), plus the next steps. Call after every recommended_block has been persisted via persist_block. The operator-facing summary (admin URL, etc.) still requires finalize_workspace afterward.
regenerate_block
Get the bundle needed to regenerate ONE v2 page block with new operator instructions. Use this when the operator asks for a targeted change to an existing block ('make the hero punchier', 'add a card about kids cuts', 'rewrite the FAQ to be less salesy'). Returns: current_props (so your LLM can iterate rather than start fresh), workspace_summary (business name, industry, services, voice from the workspace's soul), brain_patterns (anonymized cross-workspace patterns for this vertical), customization_history (previous edits — useful for understanding what NOT to revert), and the operator's new_instructions (echoed back so they're visible in your context). The next move is YOURS: fetch the block's SKILL.md via get_block_skill, generate new props that satisfy the prop schema while applying new_instructions, then call persist_block with `customization: { prompt: <new_instructions> }` to record the change. If the block has never been persisted (status=first_generation), this is a normal first-time generation path — same downstream flow, just no current_props to iterate from. Antifragile design note: this tool only ASSEMBLES context. Your LLM does the creative work. As models improve, regeneration quality improves with zero MCP changes.
get_landing_structure
Read the workspace's landing-page section list with INDEX as the addressing primitive. Returns each section's index (0..N-1, top-to-bottom on the rendered page), type ('hero', 'services-grid', 'about', 'faq', 'mid-cta', 'trust-strip', 'footer', etc.), and a 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). Use this BEFORE move_section / delete_section so you know which index to target. The preview disambiguates duplicate types (e.g. when a workspace has TWO services-grid sections — one with services, one with stats). v1.11+ replaces the v1.10 workflow where the agent had to fetch landing_pages.blueprintJson manually and parse it client-side. Cheap server-side (one DB read).
move_section
Move ONE landing-page section atomically. Identifies sections by INDEX (run get_landing_structure first to find which index to move). Splice semantics: the section at from_index is removed, then inserted at to_index in the resulting array — so to_index is the section's NEW position in the result. Examples: 'put hero below FAQ' → from_index=<hero index>, to_index=<faq's current index>. 'Move services to the top' → from_index=<services index>, to_index=0. Handles duplicate types correctly (the case reorder_landing_sections refused) — index identity is unambiguous even when two services-grid or two mid-cta sections exist. Use reorder_landing_sections instead when you want to express the entire new order at once AND types are unique. Use move_section for single-step moves OR when types repeat.
delete_section
Remove ONE landing-page section atomically. Identifies the section by INDEX (run get_landing_structure first). Refuses to leave 0 sections — minimum is 1 — so you can't accidentally wipe the page. Use when the workspace has a duplicate section type (e.g. two services-grid sections, one of which was an unintended generation artifact) and the operator wants the duplicate gone. Disambiguate WHICH duplicate via the preview text from get_landing_structure (e.g. 'stats — 4 numbers' vs '3 services (grid-3)'). For content edits, use update_landing_section. For replacing a section's content, use persist_block. delete_section is structural — it removes the section from the page entirely.
add_composite_section
Add a CUSTOM landing-page section by composing low-level primitives (heading / text / image / list / button / card / row / col / stat / embed / divider / spacer) into a tree. Use this when the operator asks for a section type that doesn't fit hero/services/about/faq/mid-cta — e.g. 'a 2-column comparison of us vs DIY', 'a pricing tier section', 'a how-it-works in 4 steps', 'a stats row with 4 numbers', 'a side-by-side image + bullet-list'. The agent's job: read operator intent + the workspace soul (voice, services, brand) and emit a `tree` JSON object. Server validates (Zod schema, depth ≤ 4, children-per-container caps, heading-level descent), voice-scans against soul.voice.avoidWords (warnings, not errors), then renders + persists. Tree root MUST be kind=section. Leaves can include kind=embed with ref ∈ {services, faq, testimonials, hours, phone} to pull workspace-data into the section without re-typing it. Phone embed renders as a tel: link. Pattern library (typical compositions): COMPARISON = section { row{cols:2, [card{heading,list-check}, card{variant:muted, heading,list-x}]}}. STATS = section { row{cols:4, [stat,stat,stat,stat]}}. HOW-IT-WORKS = section { row{cols:4, [card{heading,text}, card{heading,text}, card{heading,text}, card{heading,text}]}}. SIDE-BY-SIDE = section { row{cols:2, [col{image}, col{heading,text,button}]}}. Returns the new section's index, the full sections list with previews, validation_warnings (voice violations the agent should fix on retry), and the public_url. Use position to insert at a specific index (default: append).
update_composite_section
Replace the tree of an EXISTING composite section. Use when the operator asks to refine a custom section you previously created ('shorten the comparison', 'add another stat', 'make the cards muted'). Index must point at a section of type=composite — for typed sections (hero, services, faq, etc.) use update_landing_section, regenerate_block, or persist_block. First call get_landing_structure to find the right index — composite sections show preview text starting with 'composite — <headline>'. Then generate the new tree (typically by reading current_props equivalent — for composite sections this means fetching the existing tree, mutating, and submitting; today the simplest path is to regenerate from scratch using operator instructions + soul). Same validation + voice-scan as add_composite_section. Returns the same payload with index unchanged.
get_intake_structure
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.
add_intake_field
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. 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. 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. Position is optional — defaults to appending at the end. Use get_intake_structure first if you want to insert between specific fields.
move_intake_field
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'. Run get_intake_structure first to find the indices.
delete_intake_field
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.
update_intake_field
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. Use for content edits ('rename phone to mobile', 'make email optional', 'add a fourth option to property type', 'change the helper text'). ID changes must not collide with another field's id (server rejects). For structural changes (add/remove fields) use the dedicated tools.
get_booking_structure
Read the workspace's booking event-type + fields (indexed list with type + label + required + 1-line preview). Standard fields (fullName at index 0, email at index 1) are flagged is_standard:true — they're server-owned and cannot be moved/deleted/renamed. Use BEFORE add_booking_field / move_booking_field / delete_booking_field / update_booking_field to find the right index.
add_booking_field
Add ONE field to the booking form (after the standard fullName + email). Field shape: { id, type, label, required?, placeholder?, options? }. Types: text, textarea, email, phone, select. Use when the operator wants to capture extra info from bookers — service address, equipment type, preferred technician, party size, etc. ID must be unique within the form (cannot be 'fullName' or 'email' — those are reserved). Position defaults to appending at the end. Insert positions must be >= 2 (slots 0/1 are reserved for the standards).
move_booking_field
Move ONE booking field (extra) to a new position. Splice semantics. Standards (fullName, email at indices 0/1) cannot be moved AND cannot be displaced — both from_index and to_index must be >= 2.
delete_booking_field
Remove ONE booking field (extra). Standards (fullName, email at indices 0/1) cannot be deleted — index must be >= 2. Floor is 'just the 2 standards' (different from intake's 'minimum 1' rule because booking forms always have 2 standards).
update_booking_field
Patch ONE booking field (extra) by index. Patch can include any subset of: id, type, label, required, placeholder, options. Standards (fullName, email) cannot be patched — index must be >= 2. ID changes blocked from colliding with another field OR with reserved standard ids.
get_portal_structure
Read the workspace's portal template — indexed list of composite-tree sections with previews. Use BEFORE add_portal_section / update_portal_section / move_portal_section / delete_portal_section to find the right index. Empty templates are valid (the portal just shows built-in tabs without a Custom tab).
add_portal_section
Add a composite-tree section to the workspace's portal template. The template renders on every customer's portal — same composite primitive vocabulary as landing (12 node kinds), PLUS 5 customer.* embed refs that pull per-customer data: customer.contact_info (name + email + phone), customer.next_appointment (upcoming booking card), customer.recent_appointments (history list), customer.documents (download links), customer.deals (active jobs/deals). Read get_block_skill('composite') for the primitive vocabulary + voice rules. Tree root MUST be kind=section. Validation runs (Zod + structural rules + voice scan) same as add_composite_section. Typical patterns: WELCOME = section { headline: 'Welcome back', children: [text + customer.contact_info] }. NEXT-APPOINTMENT = section { headline: 'Your next visit', children: [embed: customer.next_appointment, button: book] }. DOCS = section { headline: 'Your documents', children: [embed: customer.documents] }. Position is optional — defaults to appending. Use get_portal_structure first if you want to insert between specific sections.
update_portal_section
Replace the tree of an existing portal-template section. Use to refine ('shorten the welcome', 'add a CTA to the documents section'). Index must exist. Validation runs same as add_portal_section.
move_portal_section
Move ONE portal-template section atomically. Splice semantics: section at from_index removed, then inserted at to_index in the result.
delete_portal_section
Remove ONE portal-template section. UNLIKE landing's delete_section, leaving 0 portal sections is valid — the portal just shows built-in tabs (Documents, Bookings) without a Custom tab.
preview_portal
Render the workspace's portal template against a SPECIFIC contact's data. Returns HTML + CSS so you can visually verify the template before customers see it. Pass contact_id of any contact in the workspace; if the id doesn't belong to this workspace, you get a 404. Use after add_portal_section / update_portal_section to confirm the per-customer embeds resolve correctly with real data.
register_partner_agency
Register a partner agency. Used by Scale-tier customers who resell SeldonFrame's Business OS to SMBs (HVAC contractors, dentists, lawyers, realtors) under their OWN brand. Once registered, the agency can attach client workspaces via attach_workspace_to_agency; those workspaces will show the agency's logo / colors / support links instead of SeldonFrame's. Plan gate: at least one workspace owned by the caller must be on Scale tier; otherwise the agency is created in 'pending' status and chrome substitution doesn't activate until the upgrade lands. Provide name (required) + slug (auto-derived from name if omitted). Optional: logo_url (uploaded image URL), primary_color / accent_color (hex like #5b21b6), support_email + support_url (where the agency's clients go for help — these REPLACE SeldonFrame's docs/Discord pointers in client chrome), hide_powered_by_badge (true to suppress the 'Powered by SeldonFrame' footer on clients' public pages — Scale-tier perk).
attach_workspace_to_agency
Attach a workspace to a partner agency. The workspace's chrome (admin dashboard logo, public landing footer, customer portal branding) flips to the agency's brand. Caller must own BOTH the agency and the workspace. Agency must be in 'active' status (not pending — register first, upgrade if needed).
detach_workspace_from_agency
Detach a workspace from its current agency. Chrome falls back to SeldonFrame defaults on next render. Either the workspace owner OR the agency owner can detach.
register_partner_agency_sender_domain
Register a sender domain for a partner agency so the agency can send transactional emails (welcome, magic-link, portal-access-code) FROM their own domain instead of welcome@seldonframe.com. The SeldonFrame backend creates the domain in Resend (under our SF Resend account), and returns the DNS records (SPF, DKIM, MX) the agency must add at THEIR registrar. The agency does NOT need their own Resend account. The default sender_local_part is 'welcome' — final sender becomes welcome@<domain>. Override with sender_local_part='hello' to get hello@<domain>. After this call: the agency adds the DNS records, waits 5-60 min for propagation, then calls verify_partner_agency_sender_domain. Once Resend confirms verification, the agency's clients' transactional emails switch to the agency's sender automatically.
verify_partner_agency_sender_domain
Trigger Resend's DNS verification for a partner agency's sender domain. Call this AFTER the agency has added the SPF/DKIM/MX records at their registrar. Returns the current verification status. When status flips to 'verified', the agency's verified_sender_at timestamp is set and chrome substitution kicks in for outbound emails on attached workspaces. Idempotent — safe to call repeatedly while DNS is propagating.
reorder_landing_sections
Reorder the sections of a workspace's landing page WITHOUT changing their content. Use when the operator says 'move FAQ to the bottom', 'put services after the about section', 'rearrange so the CTA is below testimonials'. Pass `new_order` as the full ordered array of section types as they should appear top-to-bottom. The multiset of types in new_order MUST equal the current landing's section types — no add/remove. Section types include: hero, services-grid, about, mid-cta, faq, testimonials, trust-strip, emergency-strip, service-area, partners, footer (the actual set depends on what's currently on the page). Returns the new sections_order on success or validation_errors on failure (missing/extra types, duplicates). For content edits use update_landing_section. To regenerate a block's content use regenerate_block. To get the current order, fetch the workspace's landing or call regenerate_block (which exposes block names) — most landing pages start as: hero → services → about → faq → mid-cta.
upload_workspace_image
Upload an image to a workspace and apply it to one of two slots: 'logo' (replaces organizations.theme.logoUrl, surfaces in header / footer / og-image / favicon) or 'hero_background' (replaces the hero section's background image and re-renders the landing page). Use when the operator says 'use this as my logo', 'replace the hero image with this photo', 'change the header logo'. PICK ONE source — the others are mutually exclusive: (a) `image_url` (PREFERRED, v1.10.1+) — public HTTPS URL to the image. The SF backend fetches it directly. Best path for Cloudinary, Unsplash, S3, or any image already on the web. file_name + content_type are auto-derived from the URL — you don't need to pass them. (b) `local_file_path` (v1.10.1+) — absolute path on the operator's machine. The MCP server (running locally) reads the file and forwards bytes to the backend. Best path for files on the operator's desktop. file_name + content_type derived from the path. (c) `image_data_b64` (legacy v1.10.0) — image bytes base64-encoded. Use only when you've generated bytes yourself (e.g. dynamic image gen) and there's no URL or path. Be aware: the encoded string consumes your tool-call token budget; for files >~12 KB raw, prefer (a) or (b). Max 5 MB across all paths. Allowed types: image/png, image/jpeg, image/webp, image/svg+xml, image/gif. v1.59.2 — for local_file_path and image_data_b64, the MCP server sniffs the actual bytes (magic numbers / SVG root tag) before uploading anything; a file that isn't a recognized image format is rejected locally and never reaches the network. This tool cannot be used to exfiltrate arbitrary local files under an image_url. Returns the public Blob URL on success; that URL is now live on the workspace's public surface within seconds. Antifragile design: server only validates file shape + applies URL to the right column. Your LLM picks which slot ('they said logo, that maps to slot=logo'). As you get better at intent-mapping, the harness doesn't change.
read_brain_path
Read a single brain note from the workspace's layer-1 brain. Returns the body (markdown), confidence (0-1), uses (times read), wins (times the consuming interaction was successful), and metadata. Reading a note increments its `uses` counter — that's how the feedback loop knows the note has been consumed. Use BEFORE generating blocks: check for relevant entries (voice/copy-that-works.md, customers/recurring.md, learnings.md) so your generation reflects what's been observed about this workspace.
list_brain_dir
List brain notes in the workspace's layer-1 brain. Returns metadata + a 120-char body preview per note (full body requires read_brain_path). Use to discover what the brain knows about this workspace before generating blocks. Pass `prefix` to filter by directory (e.g. 'voice/' returns voice-related notes only). Notes are returned sorted by confidence descending.
write_brain_note
Write a brain note to the workspace's layer-1 brain. Use to capture insights the operator volunteers ('walk-ins on Saturday convert 3× better', 'don't ever say synergy in the copy', 'most leads come in via Instagram'). The note is REPLACED on subsequent writes to the same path; for append-style writes use `append: true`. Source field is recorded so the cron can attribute promotions correctly.
connect_workspace
Connect this device/IDE to an EXISTING SeldonFrame workspace via magic-link email. Use when the operator already has a workspace (e.g. created from another device) and wants to admin it from this Claude Code / Cursor / Windsurf session. Sends a confirmation email with a one-click approval link; the tool polls until approval (5-min timeout) then stores the workspace bearer locally. For brand-new workspaces, use create_workspace_v2 instead.
add_custom_domain
Add a custom hostname (e.g. 'joescuts.com', 'www.joescuts.com', 'bookings.joescuts.com') to the workspace. PAID FEATURE — requires Growth ($29/mo) or Scale ($99/mo); returns 402 upgrade_required on free tier. Returns DNS instructions the operator needs to add at their registrar (Cloudflare, Namecheap, GoDaddy, etc.). Once DNS propagates (typically 5min - 24h), call verify_domain to mark verified + enable routing. Vercel auto-provisions SSL once DNS resolves.
verify_domain
Re-check DNS for a previously-added custom domain. Returns { verified: true } once Vercel sees the correct DNS record AND issues SSL — usually 5 minutes after the operator adds the CNAME / A record at their registrar, sometimes up to 24 hours depending on TTL. Returns { verified: false, recommended_records } when DNS still hasn't propagated; surface those recommendations to the operator so they can fix their registrar config.
list_workspace_domains
List all custom domains registered to the workspace. Returns hostname, status (pending / verified / failed), DNS verification record, and primary flag for each. Allowed on all tiers — free workspaces will see an empty list since custom domains require a paid tier.
remove_workspace_domain
Remove a custom domain from the workspace. Routes immediately stop responding for the removed hostname; SSL cert is preserved on Vercel for 30 days in case the operator wants to re-add it. Idempotent — no error if the domain was already removed.
list_brain_patterns
List layer-2 cross-workspace patterns. These are anonymized insights the cron has promoted from workspaces that all observed the same thing (3+ workspaces, confidence >= 0.7). Use BEFORE generating blocks for a vertical-specific business — patterns/by-vertical/<vertical>.md gives you observations across every other workspace in that vertical. Compounding moat: each new workspace's interactions feed back into these patterns over time.
configure_llm_provider
USE WHEN USER SAYS: 'set up Anthropic key for my agents', 'add my OpenAI key', 'configure BYOK for agents', 'why is my chatbot saying it's not configured?' FIRST-RUN setup BEFORE create_agent. Sets the LLM API key for this workspace's agents (BYOK — Bring Your Own Key). The OPERATOR pays the LLM provider directly (Anthropic / OpenAI / etc.); SF charges separately for agent platform usage. Stored encrypted at rest using the deployment's ENCRYPTION_KEY. Operators get keys from console.anthropic.com (recommended for v1.26.x — best tool-use support) or platform.openai.com. v1.28+ AUTO-DETECT: pass api_key='env' (or omit api_key entirely) to read process.env.ANTHROPIC_API_KEY / OPENAI_API_KEY from the MCP server's own environment. v1.59.2 — CONSENT REQUIRED before storing an env-detected key: ask the user first ('I found an Anthropic key in your environment — OK to store it in your SeldonFrame workspace?'), then re-call with confirm_store_env_key: true. Without that flag, this tool reports the env var it found and stops — it does NOT transmit the value. A key passed explicitly via api_key is consent by definition and is never gated. Returns { ok: false, error: 'no_env_key' } if the env var isn't set; in that case the user must paste the key explicitly. Skip if the workspace already has a key — agents fail-graceful with 'I'm not set up yet' if no key configured, so a 'not configured' chatbot response means CALL THIS TOOL.
create_agent
USE WHEN USER SAYS: 'add a chatbot to my website', 'add an AI assistant to my landing page', 'put a chat widget on my site', 'create a website chatbot', 'add an AI agent that answers customer questions', 'I want chat on my homepage', 'build me a chatbot for [business]'. DON'T confuse with: list_blocks (chat widgets are NOT a block type — agents are a separate primitive); send_conversation_turn (that's for inbound SMS/email auto-reply, NOT a website widget); generate_agent (for an agent that ACTS on bookings/leads/a schedule — review requester, speed-to-lead, follow-up — use generate_agent instead; THIS tool only builds a website chat widget with no trigger). If the operator wants chat on their website, THIS is the tool. Creates a new agent for this workspace. Agents are conversational interfaces (web chat, voice, SMS) that answer FAQs, book appointments, and escalate to humans — composed from typed primitives + the workspace's Soul (industry, voice, services). WHAT GETS COMPOSED AUTOMATICALLY: persona derived from soul.industry + soul.voice; FAQ knowledge from your `faq` array; pricing facts from `pricing_facts` (validators block any $-amount the agent invents that's not in this list); typed tools (look_up_availability, book_appointment, find_my_existing_appointment, escalate_to_human, provide_faq_answer). WHAT YOU PROVIDE: name, archetype (website-chatbot for v1.26.x+; voice-receptionist + sms-followup-bot queued), channel (web_chat / voice / sms / email), inline FAQ pairs, allowed pricing facts, optional greeting. STATUS LIFECYCLE: created in 'draft' (not callable). Flip to 'test' to chat with it in sandbox. Flip to 'live' once you're confident — v1.26.2+ eval-gates 'live' until 8-scenario suite passes ≥87.5%. SAFETY: response validators run on every turn — quotes_only_from_soul_pricing (critical, blocks hallucinated $X), no_prompt_injection_echo (critical), no_pii_leak (critical), no_avoid_words (warning), response_length_under_cap (warning). Critical fail = agent says 'let me check + escalate' instead of sending the bad response. OUTPUT: the agent's embed URL (one-line <script> for the operator's website) and turn URL (POST endpoint for direct API integration). Tell the operator to drop the script tag on their site OR show them the dashboard sandbox at /agents/[id]/test to chat with it.
list_agents
USE WHEN USER SAYS: 'show me my agents', 'which chatbots do I have', 'list agents in this workspace', 'is the HVAC chatbot live yet?'. Lists all agents in the workspace with status (draft/test/live/paused), version, daily token usage vs budget, and metadata. Use to find an agent_id before calling publish_agent / update_agent_blueprint / get_agent_metrics, or to audit which agents are live across a workspace.
publish_agent
USE WHEN USER SAYS: 'put my chatbot live', 'enable the agent', 'switch agent to test mode', 'pause the chatbot', 'go live with the assistant', 'turn off the chatbot temporarily'. Changes an agent's status: draft → test (sandboxed playground), test → live (real bookings, real escalations, customer-facing), live → paused (chat bubble disabled). EVAL GATE (v1.26.2+): flipping to 'live' AUTO-RUNS the 8-scenario eval suite — rejects with error='eval_gate_failed' if pass rate < 87.5%. The response includes evalSummary so you can show the operator which scenarios failed and route them to /agents/[id]/settings to fix. Use { force: true } to bypass (logged; SF emergencies only).
deploy_agent
USE WHEN USER SAYS: 'deploy this agent', 'go live with my template', 'sell this agent — get it answering', 'launch the receptionist I built', 'turn my template into a real number', 'ship this to a client'. Deploys a self-built template (source.template_id) or an installed marketplace listing (source.listing_slug) into a REAL, answering agent for THIS workspace — the deploy-to-sell / earn moment, not just a build step. Idempotent: calling it again for the same source resumes the same deployment instead of creating a duplicate. RESUMABLE TWO-STEP FLOW — read the `status` field: • status='needs_connect' → NOT live yet. `requirements`/`missing` list what's unmet (calendar OAuth, another connector, a phone number, business info) and `wizardUrl` is a ONE-TIME human-only link (calendar OAuth + Twilio number can't be done by an agent). Relay it verbatim: 'Open this once to connect your calendar / phone number: <wizardUrl>' — then, once the human confirms they've done it, call deploy_agent again with the SAME source (and `phone` if the agent needs a number) to proceed. • status='live' → deployed and answering right now. Tell the operator the number (if any) is live and taking real calls/bookings — this is revenue-generating, not a sandbox. • status='disabled' → the deploy verb isn't enabled for this environment yet. PHONE: only required when the template needs a number and none is attached yet — pass phone={mode:'forward', number:'+1...'} to forward an existing line, or phone={mode:'provision', area_code:'512'} to buy a new Twilio number (requires the workspace's own Twilio creds — surfaced as a `telephony` requirement if missing). Exactly one of source.template_id / source.listing_slug is required — template_id for something you built with create_agent-style tools in THIS workspace, listing_slug for a marketplace agent you're installing to run for a client.
generate_agent
USE WHEN USER SAYS: 'create a google review requester for my dentist office', 'text new leads instantly', 'build a speed-to-lead texter', 'set up an after-hours receptionist', 'make an agent that follows up after every booking'. Build a working AI agent from a plain-English description. It classifies the intent and assembles a deployable agent — trigger (e.g. fires on booking.completed or lead.created, or answers inbound calls/chats) + skill (the playbook it follows) + guardrails (quiet hours, rate caps, safety rubric) — all wired automatically, no manual configuration. Returns a template_id — then call deploy_agent with { source: { template_id } } to make it live and firing on its trigger (e.g. after every booking, or the instant a new lead arrives). USE THIS (not create_agent) for ANY agent that DOES something on an event or a schedule — a review request after a job, an instant reply to a new lead, a scheduled recap. create_agent is ONLY for a website chat widget with no trigger. Delivery note: a freshly generated event-outbound agent (review-requester / speed-to-lead) defaults to EMAIL so it can send a real message today without the workspace connecting a phone number first (see `warnings` in the response); mention texting/SMS explicitly in your description if you want it to try SMS instead — full delivery still needs the workspace's own Twilio connected, surfaced by deploy_agent as a 'telephony' requirement.
improve_agent
USE WHEN USER SAYS: 'improve the agent', 'make the chatbot better', 'why does the agent keep messing up', 'learn from real conversations', 'find what's going wrong and fix it', 'run the improve loop'. Replays the agent's recent REAL conversations as graded evals, clusters the failure modes, and PROPOSES a blueprint patch with before/after scores (paired per-scenario flips + an honest verdict — 'inconclusive' means the sample is too small to call). NEVER applies anything — review the proposal, then call apply_improvement. Takes 1-3 minutes (two replay passes). Needs the workspace's OWN Anthropic key (BYOK) — unbounded build/test-tier LLM cost, same gate as run_agent_evals; returns ok:false reason='no_llm_key' (402) if unset, point the operator at configure_llm_provider. Also returns ok:false (422) with a reason string when there's nothing to replay yet: 'no_conversations' (agent hasn't talked to anyone real), 'no_scenarios' (none of the sampled conversations converted cleanly), or 'agent_not_found'. On success (ok:true): `proposalId` is null when the baseline was already perfect ('nothing to improve') or no patch was proposed/it failed guardrails (note explains why) — in those cases there's nothing to apply. Otherwise pass `proposalId` straight to apply_improvement if the operator approves. `verdict` is 'better' ONLY when paired.improved - paired.regressed >= 3 AND no critical scenario regressed; 'worse' on a clear net regression; otherwise 'inconclusive' — AT THAT SAMPLE SIZE ONLY LARGE EFFECTS ARE REAL, so relay verdict='inconclusive' to the operator as: 'Small sample — apply on judgment, not on the score.' Never round an inconclusive verdict up to sounding like a pass.
apply_improvement
USE WHEN USER SAYS: 'apply that improvement', 'go with the proposed fix', 'apply the patch', 'yes, use the better version', 'accept the improve_agent proposal'. Applies a previously PROPOSED patch (from improve_agent's `proposalId`) after human review — this is the ONLY tool that can move an improve proposal's patch onto the live blueprint. Re-validates the patch against the agent's CURRENT blueprint before writing (it may have moved since the proposal was created) and creates a new agent_versions snapshot, exactly like update_agent_blueprint. Version drift doesn't block the apply — if the blueprint changed since the proposal was made, the result just carries a `note: 'applied over vN'` so you know it landed on a newer base than it was proposed against. Rejects (ok:false, 422) with a reason when the proposal can't be applied: 'not_found' (wrong id/org, or already resolved), 'not_proposed' (already applied or dismissed — improve proposals are one-shot), 'revalidation_failed' (the patch no longer passes the guardrail against the current blueprint). None of these ever touch the blueprint. After applying, the agent is on a new version — suggest re-running run_agent_evals or publish_agent({status:'live'}) (which auto-evals) to confirm the new version still passes the safety gate before the operator relies on it.
update_agent_blueprint
USE WHEN USER SAYS: 'add this FAQ to the chatbot', 'update agent pricing', 'change the greeting', 'remove booking from the agent', 'the chatbot answer for X is wrong, fix it', 'add another service to the agent'. Updates an agent's blueprint (FAQ, pricing facts, greeting, capabilities). Bumps current_version + writes a new agent_versions row for rollback. The agent's status is unchanged — flip to test/live separately. PATCH SEMANTICS: arrays REPLACE (not merge). If you want to ADD a single FAQ pair, fetch the current blueprint first via list_agents, append your new pair, and submit the full updated array. After a blueprint change, RE-RUN evals before promoting to live (use run_agent_evals or just call publish_agent({status:'live'}) which auto-runs them). Common reasons to call this: operator added new FAQ entries; pricing changed; greeting needs A/B testing; restricting capabilities (e.g. removing book_appointment to make agent answer-only).
get_workspace_state
USE FIRST for any workspace task — replaces 4-6 separate discovery calls with one. Returns: workspace identity (name, slug, industry, timezone, dashboard URL); integrations status (anthropic / openai / twilio / resend / kit / mailchimp configured? — booleans only, no keys leaked); agents WITH inline health stats (status, version, eval pass rate, validator pass rate 24h, conversations 24h, eval gate met?, last eval run); high-level counts (contacts, bookings, deals, agents); and a next_steps array tailored to the workspace's current state (e.g. 'configure Anthropic key', 'no agents yet — call build_website_chatbot', 'agents need eval run before live'). USE WHEN USER SAYS: 'what's in this workspace', 'how is my chatbot doing', 'build me a chatbot for [biz]' (call FIRST so you know if an agent already exists + if LLM is configured), 'is my agent live yet', 'workspace status'. AVOIDS asking the user obvious questions like 'how should I configure the Anthropic key?' — the response.integrations.anthropic.configured tells you. Avoids creating a duplicate agent — response.agents tells you what already exists. Avoids a separate get_agent_metrics call — stats come inline.
build_website_chatbot
USE WHEN USER SAYS: 'build me a chatbot for [business]', 'add a chatbot to my website', 'create a website chatbot', 'put a chat widget on my homepage', 'set up an AI assistant for my landing page'. ONE-CALL skill bundle that does the canonical chatbot setup end-to-end: (1) auto-configures the workspace's Anthropic LLM key from process.env.ANTHROPIC_API_KEY if no key is configured yet (most Claude Code users already have this set), or accepts an explicit anthropic_api_key arg; (2) creates a website-chatbot agent with the FAQ + pricing facts + greeting you provide; (3) publishes to status='test' so the operator can sandbox-test before going live (the eval gate runs only on 'live'); (4) returns the embed snippet, dashboard URL, and clear next-steps. USE THIS as the default for natural-language 'create a chatbot' requests. Fall back to the primitive tools (configure_llm_provider + create_agent + publish_agent) only when you need a custom flow (e.g. agency managing multiple operators with separate Anthropic billing — pass anthropic_api_key explicitly per workspace). v1.59.2 — CONSENT REQUIRED before storing an env-detected key: if no anthropic_api_key is passed and process.env.ANTHROPIC_API_KEY is found, this tool does NOT store it on the first call. It reports what it found and stops (nothing else in the bundle runs yet). Ask the user for permission, then re-call with confirm_store_env_key: true to proceed with the full bundle. If neither an explicit key nor an env key is present, the bundle proceeds anyway on the SeldonFrame platform key — no consent needed because nothing of the operator's is transmitted.
update_website_chatbot
USE WHEN USER SAYS: 'update the chatbot's FAQ', 'add new pricing to the agent', 'change the greeting', 'add a new service to the chatbot', 'the chatbot answer for X needs updating'. ONE-CALL bundle for updating an existing website-chatbot (peer to build_website_chatbot which CREATES). Looks up the workspace's website-chatbot agent (or accepts an explicit agent_id), merges your patch into the current blueprint, bumps version, returns refreshed embed_url + dashboard_url + version + next_steps. PATCH SEMANTICS: arrays REPLACE (not merge) per update_agent_blueprint convention — pass the FULL desired faq[] / pricing_facts[], not a delta. Greeting + capabilities are scalar replaces. If you want to ADD one FAQ pair, fetch current via get_workspace_state first and submit the full updated array. AFTER UPDATE: re-run evals (call run_agent_evals or use the dashboard) before promoting back to live, since blueprint changes can affect agent behavior.
embed_chatbot_on_workspace_landing
USE WHEN USER SAYS: 'add the chatbot to my landing page', 'embed this on the website', 'put the agent on every page', 'make the chat bubble appear', 'wire the chatbot to the public site'. Wires a published agent's embed.js URL into the workspace's organization settings. The public page renderer (/s/ + /l/ routes) reads this on every render and injects a <script src='...' async></script> tag near </body>, so the floating chat bubble appears bottom-right on every page of the workspace's public surface — no per-section editing, no manual HTML, no Pages → Edit step. Agent must already be in status='test' or status='live' (call publish_agent first if it's still draft). One workspace = one chatbot at a time; calling this again with a different agent_id replaces the bubble. Pair with remove_chatbot_from_landing to clear it.
remove_chatbot_from_landing
USE WHEN USER SAYS: 'remove the chatbot from the landing', 'take the chat bubble off the page', 'unembed the chatbot', 'hide the chat from visitors'. Clears the workspace's chatbot embed setting so the public page renderer stops injecting the script tag. The agent itself is NOT deleted — it's still available via /agents/[id]/test for sandbox conversations and can be re-embedded with embed_chatbot_on_workspace_landing.
run_agent_evals
USE WHEN USER SAYS: 'test the chatbot against safety scenarios', 'run evals', 'check if my agent passes the safety suite', 'is the chatbot safe?', 'why did my agent fail the publish gate?'. Runs the platform's 8-scenario safety + behavior eval suite against this agent: prompt-injection probes (ignore-instructions, role-swap), PII probes (customer-list leak), pricing discipline (refuses invented prices, refuses competitor match), scope refusal (off-topic), greeting + escalation. Each scenario runs through the live blueprint as an ephemeral test conversation; results persist to agent_evals. publish_agent({status:'live'}) AUTOMATICALLY runs this and gates on ≥87.5% pass. Call THIS tool directly to dry-run before publishing or to verify after a blueprint update.
tail_agent_conversations
USE WHEN USER SAYS: 'show me recent chats with the agent', 'what are customers asking the chatbot', 'tail conversations', 'list the latest 20 chatbot sessions', 'what's been happening on the agent today?'. Lists recent conversations for an agent — newest first. Excludes eval-runs and replay-runs by default (set include_eval_runs=true to see them). Each row includes status, turn_count, tokens, llm_cost_cents, and the customer's first message preview so you can spot patterns (most common questions, escalations, etc.) without opening each transcript. Use BEFORE get_agent_conversation to pick which conversation to drill into.
get_agent_conversation
USE WHEN USER SAYS: 'show me that conversation in detail', 'why did the chatbot say X?', 'debug this chat', 'what tools did the agent call in conversation Y', 'show the validator results for conversation Z'. Fetches the full transcript of a single conversation: every turn (user + assistant), all tool_calls (look_up_availability, book_appointment, escalate_to_human, etc.) with their inputs, all tool_results with success/error, validator_results per assistant turn (which validators passed/failed), tokens, latency, model. Use this to debug WHY an agent gave a specific answer — was it a tool failure? a validator gating? wrong info in blueprint? Pair with replay_conversation to test a blueprint fix.
replay_conversation
USE WHEN USER SAYS: 'will my new FAQ break the booking flow?', 'replay this chat against the new blueprint', 'regression test the chatbot', 'test if my recent change still answers this conversation correctly'. Replays a past conversation's user messages against the agent's CURRENT blueprint, returning the original responses + the new responses side-by-side. Lets you regression-test a blueprint change without touching production. Creates a new ephemeral test-status conversation tagged with replay_of=<original>; the original is untouched.
get_agent_metrics
USE WHEN USER SAYS: 'is my chatbot healthy?', 'how's the agent performing?', 'show me agent stats', 'what's my chatbot's pass rate this week?', 'agent dashboard ping'. Aggregate health stats for an agent over a time window: conversations + turns count, tokens (in/out), avg latency, validator pass rate (% of assistant turns where ALL validators passed), latest eval pass rate (last result per scenario). Use as a dashboard ping. If validator_pass_rate drops or eval_pass_rate falls below the 87.5% gate, the agent shouldn't be promoted to live.
register_partner_agency
Create a partner-agency entity for white-label SaaS reselling. STEP 1 of agency onboarding. The agency owns multiple client workspaces; chrome (brand name, logo, colors, support URLs) is substituted on each workspace when its parent_agency_id is set to this agency. Plan-gate: caller must own a workspace on the Scale tier ($99/mo). If not, the agency is created in 'pending' status (gated_pending=true) — caller upgrades a workspace and re-runs to flip it. After this call succeeds: optionally register a sender domain (register_partner_agency_sender_domain), then attach client workspaces (attach_workspace_to_partner_agency). Example: register_partner_agency({ name: "Acme Digital", primary_color: "#1FAE85", support_email: "help@acmedigital.com" })
register_partner_agency_sender_domain
Register a custom email-sender domain for a partner-agency. STEP 2 of agency onboarding (optional but recommended — without it, agency emails ship from welcome@seldonframe.com). Calls Resend's /domains endpoint, persists the resend_domain_id on the agency row, and returns the DNS records the agency must add at their registrar (Cloudflare / Namecheap / GoDaddy / etc.). DNS propagation typically takes 5-60 minutes. After propagation, call verify_partner_agency_sender_domain to poll Resend + flip verified_sender_at. Requires: RESEND_API_KEY configured on the SeldonFrame backend. Example: register_partner_agency_sender_domain({ agency_id: "uuid", domain: "acmedigital.com", sender_local_part: "hello" })
verify_partner_agency_sender_domain
Poll Resend for sender-domain verification status. STEP 3 of agency onboarding. Run this after the agency has added the DNS records returned by register_partner_agency_sender_domain and DNS has had time to propagate (usually 5-60 minutes; some registrars can take longer). On success: sets verified_sender_at on the agency row + populates sender_email_address. The branding resolver then exposes the verified sender to outbound email paths automatically. Idempotent + safe to call repeatedly while waiting on DNS. Example: verify_partner_agency_sender_domain({ agency_id: "uuid" })
attach_workspace_to_partner_agency
Attach a client workspace to a partner-agency so chrome substitution applies on that workspace's operator-facing surfaces. STEP 4 of agency onboarding — run once per client workspace the agency manages. Caller must own both the workspace AND the agency. Effect is immediate: next page load in that workspace renders the agency's brand_name, logo, colors, support URLs (replacing SeldonFrame's defaults). The workspace's own data (contacts, bookings, agents, etc.) is unchanged. Reversible via detach_workspace_from_partner_agency. Example: attach_workspace_to_partner_agency({ workspace_id: "phoenix-hvac-uuid", agency_id: "acme-digital-uuid" })
detach_workspace_from_partner_agency
Detach a client workspace from its parent partner-agency. The workspace's chrome falls back to SeldonFrame defaults on next page load. Use when an agency loses a client or when the workspace transitions out of agency management. Caller must own the workspace OR own the agency the workspace is attached to (either authorizes detach). Workspace data is unchanged; only parent_agency_id is set to null. Reversible via attach_workspace_to_partner_agency. Example: detach_workspace_from_partner_agency({ workspace_id: "phoenix-hvac-uuid" })
customize_landing
Apply a natural-language instruction to the current R1 landing page for a workspace. The LLM rewrites only the parts of the payload touched by the instruction, preserving everything else. Each call creates an immutable version snapshot so the operator can undo any change. Edits go live immediately (no publish step). Examples: • customize_landing({ workspace_id: '...', instruction: 'Change the hero tagline to focus on 24/7 emergency service' }) • customize_landing({ workspace_id: '...', instruction: 'Replace the hero photo with https://example.com/photo.jpg' }) • customize_landing({ workspace_id: '...', instruction: 'Make the FAQ more local-flavored for Phoenix residents' }) • customize_landing({ workspace_id: '...', instruction: 'Add a question about financing options to the FAQ' }) Requires: the workspace must have an R1 landing page (auto-generated during workspace creation). Requires: an Anthropic API key configured for the workspace (BYOK via /settings/integrations or platform fallback). Returns: { ok: true, summary, version_id } on success. Returns: { ok: false, error: 'no_landing_exists' } if the workspace has no R1 landing yet.
list_landing_versions
Return the edit history for a workspace's R1 landing page, newest first. Each row has: id, instruction, summary, createdAt. Pass a version id to customize_landing_revert to undo to that point. Example: list_landing_versions({ workspace_id: '...' })
revert_landing
Revert the R1 landing page to a prior version's payload snapshot. Creates a new immutable version row (never deletes history). The page goes live immediately with the reverted content. Get version IDs from list_landing_versions. Example: revert_landing({ workspace_id: '...', version_id: '<uuid>' })
Claude Desktop / Cursor
Paste into your MCP client config file to install this server.
{
"mcpServers": {
"seldonframe": {
"seldonframe": {
"command": "npx",
"args": [
"-y",
"@seldonframe/mcp@latest"
]
}
}
}
}
McpServers
{
"seldonframe": {
"command": "npx",
"args": [
"-y",
"@seldonframe/mcp@latest"
]
}
}
SeldonFrame is an open-source, MCP-native Business OS. One conversation in Claude Code or Cursor creates a live workspace — website, booking page, intake form, CRM, and an AI receptionist — on a real subdomain in about 3 minutes.
Sign in to leave a review
Use Google, GitHub, or an email account so ratings stay tied to real people.
No reviews posted yet.
