@brainerce/mcp-server 3.2.0 → 3.4.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/dist/bin/http.js +377 -4
- package/dist/bin/stdio.js +377 -4
- package/dist/index.js +377 -4
- package/dist/index.mjs +377 -4
- package/package.json +53 -53
package/dist/index.mjs
CHANGED
|
@@ -1856,6 +1856,265 @@ const admin = new BrainerceClient({ apiKey: 'brainerce_...' });
|
|
|
1856
1856
|
// OAuth: getOAuthProviders(), configureOAuthProvider()
|
|
1857
1857
|
\`\`\``;
|
|
1858
1858
|
}
|
|
1859
|
+
function getContactInquiriesSection() {
|
|
1860
|
+
return `## Contact Inquiries & Forms (optional)
|
|
1861
|
+
|
|
1862
|
+
Accept messages from one or more contact forms on your storefront. The merchant
|
|
1863
|
+
configures everything in the Brainerce dashboard under **Customers \u2192 Contact
|
|
1864
|
+
Forms**; submissions land in **Customers \u2192 Inquiries** and both sides receive
|
|
1865
|
+
email notifications.
|
|
1866
|
+
|
|
1867
|
+
**IMPORTANT for AI code generators:** the merchant OWNS the form shape. Do not
|
|
1868
|
+
hardcode labels, placeholders, help text, the submit button, or the success
|
|
1869
|
+
message. Render from the schema returned by \`contactForms.get()\` so the
|
|
1870
|
+
storefront reflects whatever the merchant configures \u2014 including new fields
|
|
1871
|
+
they add later, translations they add later, and the success message they
|
|
1872
|
+
write. This is the difference between "a contact form" and "THE merchant's
|
|
1873
|
+
contact form."
|
|
1874
|
+
|
|
1875
|
+
### Two integration paths
|
|
1876
|
+
|
|
1877
|
+
1. **Simple (legacy)** \u2014 pass \`{ name, email, subject, message }\` and submit.
|
|
1878
|
+
Works out of the box, no dashboard setup needed. Backed by a lazily-seeded
|
|
1879
|
+
default form keyed \`"main"\` with five built-in fields (\`name\`, \`email\`,
|
|
1880
|
+
\`phone\`, \`subject\`, \`message\`). Choose this only for throwaway demos.
|
|
1881
|
+
2. **Flexible (recommended, default in \`npx create-brainerce-store\`)** \u2014
|
|
1882
|
+
merchants can configure multiple forms per store (e.g. \`main\`,
|
|
1883
|
+
\`newsletter\`, \`whatsapp_prechat\`), add custom fields (TEXT, TEXTAREA,
|
|
1884
|
+
EMAIL, PHONE, NUMBER, SELECT, MULTI_SELECT, CHECKBOX, URL, DATE), translate
|
|
1885
|
+
everything per locale, hide any non-essential built-in. Fetch the schema
|
|
1886
|
+
with \`contactForms.get(formKey, locale)\` and render dynamically.
|
|
1887
|
+
|
|
1888
|
+
### Simple path (legacy shape)
|
|
1889
|
+
|
|
1890
|
+
\`\`\`typescript
|
|
1891
|
+
import type { CreateInquiryInput, CreateInquiryResponse } from 'brainerce';
|
|
1892
|
+
|
|
1893
|
+
const result = await brainerce.createInquiry({
|
|
1894
|
+
name: 'Jane Doe',
|
|
1895
|
+
email: 'jane@example.com',
|
|
1896
|
+
subject: 'Question about shipping',
|
|
1897
|
+
message: 'Hi, do you ship internationally?',
|
|
1898
|
+
phone: '+1-555-0100', // optional
|
|
1899
|
+
customerId: customer?.id, // optional \u2014 link to logged-in customer
|
|
1900
|
+
metadata: { page: '/contact' }, // optional
|
|
1901
|
+
});
|
|
1902
|
+
// \u2192 { id, status: 'NEW', createdAt }
|
|
1903
|
+
\`\`\`
|
|
1904
|
+
|
|
1905
|
+
### Flexible path \u2014 end-to-end contract
|
|
1906
|
+
|
|
1907
|
+
**Endpoints:**
|
|
1908
|
+
|
|
1909
|
+
| Method | Path | Returns |
|
|
1910
|
+
| ------ | ----------------------------------------------------------------- | ------------------------------ |
|
|
1911
|
+
| GET | \`/stores/:storeId/contact-forms\` | \`ContactFormSummary[]\` |
|
|
1912
|
+
| GET | \`/stores/:storeId/contact-forms/:formKey?locale=xx\` | \`ContactFormPublic\` |
|
|
1913
|
+
| POST | \`/stores/:storeId/inquiries\` | \`CreateInquiryResponse\` |
|
|
1914
|
+
|
|
1915
|
+
All three are public (no API key). The SDK wraps them so you never call them
|
|
1916
|
+
directly \u2014 use \`brainerce.contactForms.list()\`, \`brainerce.contactForms.get()\`,
|
|
1917
|
+
and \`brainerce.createInquiry()\`.
|
|
1918
|
+
|
|
1919
|
+
\`\`\`typescript
|
|
1920
|
+
import type { ContactFormPublic } from 'brainerce';
|
|
1921
|
+
|
|
1922
|
+
// List active forms (optional \u2014 if you want a form picker or route-per-form setup)
|
|
1923
|
+
const forms = await brainerce.contactForms.list();
|
|
1924
|
+
// \u2192 [{ key: 'main', name: 'Contact us', isDefault: true }, ...]
|
|
1925
|
+
|
|
1926
|
+
// Fetch one form's schema \u2014 server pre-resolves translations for the locale
|
|
1927
|
+
// and strips every field the merchant marked isVisible=false.
|
|
1928
|
+
const form = await brainerce.contactForms.get('main', 'en');
|
|
1929
|
+
// \u2192 {
|
|
1930
|
+
// id, key, name, description?, submitButton, successMessage,
|
|
1931
|
+
// fields: [{ key, type, label, placeholder?, helpText?, isRequired,
|
|
1932
|
+
// enumValues?, validation?, defaultValue? }, ...]
|
|
1933
|
+
// }
|
|
1934
|
+
|
|
1935
|
+
// Submit a keyed payload. Unknown keys are stripped, required fields are
|
|
1936
|
+
// enforced, and every value is validated against \`validation\` server-side.
|
|
1937
|
+
await brainerce.createInquiry({
|
|
1938
|
+
formKey: 'main',
|
|
1939
|
+
fields: {
|
|
1940
|
+
email: 'jane@example.com', // built-in keys
|
|
1941
|
+
message: 'Hi...',
|
|
1942
|
+
// ...any custom keys the merchant added via the dashboard
|
|
1943
|
+
},
|
|
1944
|
+
locale: 'en', // stored on the inquiry \u2014 lets staff filter by language
|
|
1945
|
+
sourceMetadata: { page: '/contact' }, // arbitrary provenance (UTM, referrer, etc.)
|
|
1946
|
+
});
|
|
1947
|
+
\`\`\`
|
|
1948
|
+
|
|
1949
|
+
Both shapes go to the same POST endpoint and may be mixed; \`fields\` wins when
|
|
1950
|
+
both provide the same key. Unknown keys (not defined on the form schema) are
|
|
1951
|
+
stripped server-side.
|
|
1952
|
+
|
|
1953
|
+
### Dynamic rendering \u2014 reference implementation
|
|
1954
|
+
|
|
1955
|
+
Render every field type the merchant can pick. Keep this as a \`<DynamicField>\`
|
|
1956
|
+
component so the form is fully driven by \`schema.fields\`.
|
|
1957
|
+
|
|
1958
|
+
\`\`\`tsx
|
|
1959
|
+
import type { ContactFormPublic, ContactFormPublicField } from 'brainerce';
|
|
1960
|
+
|
|
1961
|
+
type FieldValue = string | string[] | boolean;
|
|
1962
|
+
|
|
1963
|
+
function defaultValueFor(f: ContactFormPublicField): FieldValue {
|
|
1964
|
+
if (f.type === 'CHECKBOX') return false;
|
|
1965
|
+
if (f.type === 'MULTI_SELECT') return [];
|
|
1966
|
+
return f.defaultValue ?? '';
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
function isEmpty(v: FieldValue): boolean {
|
|
1970
|
+
if (typeof v === 'string') return v.trim().length === 0;
|
|
1971
|
+
if (Array.isArray(v)) return v.length === 0;
|
|
1972
|
+
return v === false;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
function DynamicField({
|
|
1976
|
+
field,
|
|
1977
|
+
value,
|
|
1978
|
+
onChange,
|
|
1979
|
+
}: {
|
|
1980
|
+
field: ContactFormPublicField;
|
|
1981
|
+
value: FieldValue;
|
|
1982
|
+
onChange: (v: FieldValue) => void;
|
|
1983
|
+
}) {
|
|
1984
|
+
const id = \`contact-\${field.key}\`;
|
|
1985
|
+
const { minLength, maxLength, min, max, pattern } = field.validation ?? {};
|
|
1986
|
+
const strVal = typeof value === 'string' ? value : '';
|
|
1987
|
+
|
|
1988
|
+
// Label \u2014 USE \`field.label\`, NEVER hardcode. Falls back to the key.
|
|
1989
|
+
const label = (
|
|
1990
|
+
<label htmlFor={id} className="mb-1.5 block text-sm font-medium">
|
|
1991
|
+
{field.label}
|
|
1992
|
+
{field.isRequired && <span aria-hidden className="text-red-500"> *</span>}
|
|
1993
|
+
</label>
|
|
1994
|
+
);
|
|
1995
|
+
const help = field.helpText ? <p className="mt-1 text-xs opacity-70">{field.helpText}</p> : null;
|
|
1996
|
+
|
|
1997
|
+
switch (field.type) {
|
|
1998
|
+
case 'TEXTAREA':
|
|
1999
|
+
return (<div>{label}<textarea id={id} required={field.isRequired} maxLength={maxLength} minLength={minLength} rows={6} placeholder={field.placeholder} value={strVal} onChange={(e) => onChange(e.target.value)} />{help}</div>);
|
|
2000
|
+
case 'EMAIL':
|
|
2001
|
+
return (<div>{label}<input id={id} type="email" required={field.isRequired} autoComplete="email" placeholder={field.placeholder} value={strVal} onChange={(e) => onChange(e.target.value)} />{help}</div>);
|
|
2002
|
+
case 'PHONE':
|
|
2003
|
+
return (<div>{label}<input id={id} type="tel" required={field.isRequired} autoComplete="tel" placeholder={field.placeholder} value={strVal} onChange={(e) => onChange(e.target.value)} />{help}</div>);
|
|
2004
|
+
case 'URL':
|
|
2005
|
+
return (<div>{label}<input id={id} type="url" required={field.isRequired} placeholder={field.placeholder} value={strVal} onChange={(e) => onChange(e.target.value)} />{help}</div>);
|
|
2006
|
+
case 'NUMBER':
|
|
2007
|
+
return (<div>{label}<input id={id} type="number" required={field.isRequired} min={min} max={max} placeholder={field.placeholder} value={strVal} onChange={(e) => onChange(e.target.value)} />{help}</div>);
|
|
2008
|
+
case 'DATE':
|
|
2009
|
+
return (<div>{label}<input id={id} type="date" required={field.isRequired} value={strVal} onChange={(e) => onChange(e.target.value)} />{help}</div>);
|
|
2010
|
+
case 'SELECT':
|
|
2011
|
+
return (<div>{label}<select id={id} required={field.isRequired} value={strVal} onChange={(e) => onChange(e.target.value)}><option value="">\u2014</option>{field.enumValues?.map((o) => (<option key={o.value} value={o.value}>{o.label}</option>))}</select>{help}</div>);
|
|
2012
|
+
case 'MULTI_SELECT': {
|
|
2013
|
+
const arr = Array.isArray(value) ? value : [];
|
|
2014
|
+
return (<div>{label}<div>{field.enumValues?.map((o) => { const checked = arr.includes(o.value); return (<label key={o.value}><input type="checkbox" checked={checked} onChange={(e) => onChange(e.target.checked ? [...arr, o.value] : arr.filter((v) => v !== o.value))} /><span>{o.label}</span></label>); })}</div>{help}</div>);
|
|
2015
|
+
}
|
|
2016
|
+
case 'CHECKBOX':
|
|
2017
|
+
return (<div><label htmlFor={id}><input id={id} type="checkbox" required={field.isRequired} checked={value === true} onChange={(e) => onChange(e.target.checked)} /><span>{field.label}</span></label>{help}</div>);
|
|
2018
|
+
case 'TEXT':
|
|
2019
|
+
default:
|
|
2020
|
+
return (<div>{label}<input id={id} type="text" required={field.isRequired} maxLength={maxLength} minLength={minLength} pattern={pattern} placeholder={field.placeholder} value={strVal} onChange={(e) => onChange(e.target.value)} />{help}</div>);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
export function ContactPage() {
|
|
2025
|
+
const [schema, setSchema] = useState<ContactFormPublic | null>(null);
|
|
2026
|
+
const [values, setValues] = useState<Record<string, FieldValue>>({});
|
|
2027
|
+
const [honeypot, setHoneypot] = useState('');
|
|
2028
|
+
const [sent, setSent] = useState(false);
|
|
2029
|
+
const [loading, setLoading] = useState(false);
|
|
2030
|
+
|
|
2031
|
+
// Read the current locale from the <html lang> attribute (or your own i18n context).
|
|
2032
|
+
const locale = typeof document !== 'undefined' ? document.documentElement.lang || undefined : undefined;
|
|
2033
|
+
|
|
2034
|
+
useEffect(() => {
|
|
2035
|
+
brainerce.contactForms.get('main', locale).then((form) => {
|
|
2036
|
+
setSchema(form);
|
|
2037
|
+
const initial: Record<string, FieldValue> = {};
|
|
2038
|
+
for (const f of form.fields) initial[f.key] = defaultValueFor(f);
|
|
2039
|
+
setValues(initial);
|
|
2040
|
+
});
|
|
2041
|
+
}, [locale]);
|
|
2042
|
+
|
|
2043
|
+
if (!schema) return null;
|
|
2044
|
+
|
|
2045
|
+
const onSubmit = async (e: React.FormEvent) => {
|
|
2046
|
+
e.preventDefault();
|
|
2047
|
+
if (loading) return;
|
|
2048
|
+
if (honeypot.trim().length > 0) { setSent(true); return; } // bot \u2192 silent success
|
|
2049
|
+
|
|
2050
|
+
setLoading(true);
|
|
2051
|
+
try {
|
|
2052
|
+
const payload: Record<string, unknown> = {};
|
|
2053
|
+
for (const f of schema.fields) {
|
|
2054
|
+
const raw = values[f.key];
|
|
2055
|
+
if (isEmpty(raw)) continue;
|
|
2056
|
+
payload[f.key] = typeof raw === 'string' ? raw.trim() : raw;
|
|
2057
|
+
}
|
|
2058
|
+
await brainerce.createInquiry({ formKey: schema.key, fields: payload, locale });
|
|
2059
|
+
setSent(true);
|
|
2060
|
+
} finally {
|
|
2061
|
+
setLoading(false);
|
|
2062
|
+
}
|
|
2063
|
+
};
|
|
2064
|
+
|
|
2065
|
+
if (sent) {
|
|
2066
|
+
// RENDER THE MERCHANT'S success message \u2014 do not hardcode.
|
|
2067
|
+
return <div>{schema.successMessage}</div>;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
return (
|
|
2071
|
+
<form onSubmit={onSubmit} noValidate>
|
|
2072
|
+
<h1>{schema.name}</h1>
|
|
2073
|
+
{schema.description && <p>{schema.description}</p>}
|
|
2074
|
+
|
|
2075
|
+
{/* Honeypot \u2014 must be visually hidden from humans, not from bots */}
|
|
2076
|
+
<div aria-hidden style={{ position: 'absolute', left: '-10000px', width: 0, height: 0, overflow: 'hidden' }}>
|
|
2077
|
+
<label htmlFor="contact-honeypot">Leave this field empty</label>
|
|
2078
|
+
<input id="contact-honeypot" type="text" tabIndex={-1} autoComplete="off"
|
|
2079
|
+
value={honeypot} onChange={(e) => setHoneypot(e.target.value)} />
|
|
2080
|
+
</div>
|
|
2081
|
+
|
|
2082
|
+
{schema.fields.map((field) => (
|
|
2083
|
+
<DynamicField key={field.key} field={field}
|
|
2084
|
+
value={values[field.key] ?? defaultValueFor(field)}
|
|
2085
|
+
onChange={(v) => setValues((p) => ({ ...p, [field.key]: v }))} />
|
|
2086
|
+
))}
|
|
2087
|
+
|
|
2088
|
+
<button type="submit" disabled={loading}>
|
|
2089
|
+
{loading ? '\u2026' : schema.submitButton}
|
|
2090
|
+
</button>
|
|
2091
|
+
</form>
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
\`\`\`
|
|
2095
|
+
|
|
2096
|
+
### Rules
|
|
2097
|
+
|
|
2098
|
+
- **Rate limit:** 3 submissions / 60s per IP \u2014 show a friendly "try again later" message on HTTP 429.
|
|
2099
|
+
- **Honeypot:** always render a hidden field named \`honeypot\` and **never send** it. Bots fill every input; the server rejects submissions carrying a non-empty \`honeypot\`.
|
|
2100
|
+
- **Built-in keys:** \`name\`, \`email\`, \`phone\`, \`subject\`, \`message\` always exist on the default form; the legacy \`createInquiry\` shape keeps working forever.
|
|
2101
|
+
- **Max values:** each field value is capped at 10 000 chars server-side. Validate client-side before submitting using \`field.validation.maxLength\`.
|
|
2102
|
+
- **Required fields:** respect \`field.isRequired\`. The server validates too, but show \`required\` on the input for browser-level UX.
|
|
2103
|
+
- **Validation:** when \`field.validation.pattern\` is present, pass it as the input's \`pattern\` attribute; the server also enforces it. Likewise \`minLength\`/\`maxLength\`/\`min\`/\`max\`.
|
|
2104
|
+
- **Enum values:** SELECT and MULTI_SELECT always return \`enumValues\` (non-empty array). Render from \`enumValues\`, never a hardcoded list.
|
|
2105
|
+
- **Visibility:** the server strips fields with \`isVisible=false\`, so \`schema.fields\` only contains things to render.
|
|
2106
|
+
- **Origin check:** the endpoint validates \`Origin\` against the store's allowed origins. Test from your real storefront URL, not \`file://\` or localhost unless the merchant added it to allowed origins.
|
|
2107
|
+
- **Locale handling:** always pass \`locale\` to \`contactForms.get()\` and to \`createInquiry()\`. The staff inbox filters inquiries by language. Omit to fall back to the store's default language.
|
|
2108
|
+
- **Success message:** render \`schema.successMessage\` as-is. (Variable interpolation like \`{{customerName}}\` is not yet wired; any such placeholders currently render literally. Safe to use plain text.)
|
|
2109
|
+
- **Caching:** the schema GET is safe to cache per \`{storeId, formKey, locale}\`. 60 s feels live without hammering the API.
|
|
2110
|
+
|
|
2111
|
+
### Multiple forms
|
|
2112
|
+
|
|
2113
|
+
If the merchant set up more than one form (e.g. a \`main\` form on \`/contact\`
|
|
2114
|
+
and a \`newsletter\` form embedded in the footer), render each form from its
|
|
2115
|
+
own \`contactForms.get(key, locale)\` call. The \`formKey\` you pass to
|
|
2116
|
+
\`createInquiry\` must match.`;
|
|
2117
|
+
}
|
|
1859
2118
|
function getSectionByTopic(topic, connectionId, currency) {
|
|
1860
2119
|
const cid = connectionId || "vc_YOUR_CONNECTION_ID";
|
|
1861
2120
|
const cur = currency || "USD";
|
|
@@ -1894,6 +2153,8 @@ function getSectionByTopic(topic, connectionId, currency) {
|
|
|
1894
2153
|
return getTypeQuickReference();
|
|
1895
2154
|
case "admin":
|
|
1896
2155
|
return getAdminApiSection();
|
|
2156
|
+
case "inquiries":
|
|
2157
|
+
return getContactInquiriesSection();
|
|
1897
2158
|
case "all":
|
|
1898
2159
|
return [
|
|
1899
2160
|
"# Brainerce SDK \u2014 full topic dump",
|
|
@@ -1970,10 +2231,14 @@ function getSectionByTopic(topic, connectionId, currency) {
|
|
|
1970
2231
|
"",
|
|
1971
2232
|
"---",
|
|
1972
2233
|
"",
|
|
1973
|
-
getAdminApiSection()
|
|
2234
|
+
getAdminApiSection(),
|
|
2235
|
+
"",
|
|
2236
|
+
"---",
|
|
2237
|
+
"",
|
|
2238
|
+
getContactInquiriesSection()
|
|
1974
2239
|
].join("\n");
|
|
1975
2240
|
default:
|
|
1976
|
-
return `Unknown topic: "${topic}". Available topics: setup, products, cart, checkout, checkout-custom-fields, payment, auth, order-confirmation, inventory, discounts, recommendations, product-customization-fields, tax, i18n, critical-rules, type-reference, admin, all`;
|
|
2241
|
+
return `Unknown topic: "${topic}". Available topics: setup, products, cart, checkout, checkout-custom-fields, payment, auth, order-confirmation, inventory, discounts, recommendations, product-customization-fields, tax, i18n, critical-rules, type-reference, admin, inquiries, all`;
|
|
1977
2242
|
}
|
|
1978
2243
|
}
|
|
1979
2244
|
|
|
@@ -1999,6 +2264,7 @@ var GET_SDK_DOCS_SCHEMA = {
|
|
|
1999
2264
|
"type-reference",
|
|
2000
2265
|
"i18n",
|
|
2001
2266
|
"admin",
|
|
2267
|
+
"inquiries",
|
|
2002
2268
|
"all"
|
|
2003
2269
|
]).describe("The SDK documentation topic to retrieve"),
|
|
2004
2270
|
connectionId: z.string().optional().describe("Vibe-coded connection ID (starts with vc_). Used to personalize setup code."),
|
|
@@ -2659,6 +2925,102 @@ interface CartBundleOffer {
|
|
|
2659
2925
|
requiresVariantSelection: boolean;
|
|
2660
2926
|
lockedVariant?: { id: string; name?: string };
|
|
2661
2927
|
}`;
|
|
2928
|
+
var INQUIRIES_TYPES = `// ---- Contact Inquiries & Forms ----
|
|
2929
|
+
|
|
2930
|
+
// Two shapes \u2014 legacy and flexible. The two may be mixed; \`fields\` wins on key collision.
|
|
2931
|
+
interface CreateInquiryInput {
|
|
2932
|
+
// Legacy shape (still supported forever, backed by default "main" form)
|
|
2933
|
+
name?: string; // max 120 chars (if provided)
|
|
2934
|
+
email?: string; // must be valid email if provided
|
|
2935
|
+
subject?: string; // max 200 chars
|
|
2936
|
+
message?: string; // max 10000 chars
|
|
2937
|
+
phone?: string;
|
|
2938
|
+
|
|
2939
|
+
// Flexible shape
|
|
2940
|
+
formKey?: string; // defaults to "main"; merchant-defined keys e.g. "newsletter"
|
|
2941
|
+
fields?: Record<string, unknown>; // bag of values keyed by field key (built-in or custom)
|
|
2942
|
+
locale?: string; // e.g. "en", "he" \u2014 stored on the inquiry
|
|
2943
|
+
sourceMetadata?: Record<string, unknown>; // arbitrary context (e.g. { page: '/contact', campaign: 'fall-2026' })
|
|
2944
|
+
|
|
2945
|
+
// Shared
|
|
2946
|
+
customerId?: string; // link to logged-in customer
|
|
2947
|
+
metadata?: Record<string, unknown>; // deprecated alias of sourceMetadata
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
interface CreateInquiryResponse {
|
|
2951
|
+
id: string;
|
|
2952
|
+
status: 'NEW';
|
|
2953
|
+
createdAt: string; // ISO datetime
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
// ---- Form schema (for dynamic rendering) ----
|
|
2957
|
+
|
|
2958
|
+
type ContactFormFieldType =
|
|
2959
|
+
| 'TEXT' | 'TEXTAREA' | 'EMAIL' | 'PHONE' | 'NUMBER'
|
|
2960
|
+
| 'SELECT' | 'MULTI_SELECT' | 'CHECKBOX' | 'URL' | 'DATE';
|
|
2961
|
+
|
|
2962
|
+
interface ContactFormFieldValidation {
|
|
2963
|
+
minLength?: number;
|
|
2964
|
+
maxLength?: number;
|
|
2965
|
+
min?: number;
|
|
2966
|
+
max?: number;
|
|
2967
|
+
pattern?: string; // regex string
|
|
2968
|
+
patternMessage?: string;
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
interface ContactFormPublicField {
|
|
2972
|
+
key: string; // stable identifier, e.g. "email", "company"
|
|
2973
|
+
type: ContactFormFieldType;
|
|
2974
|
+
label: string; // already localized for the requested locale
|
|
2975
|
+
placeholder?: string; // already localized
|
|
2976
|
+
helpText?: string; // already localized
|
|
2977
|
+
isRequired: boolean;
|
|
2978
|
+
enumValues?: { value: string; label: string }[]; // present (non-empty) for SELECT / MULTI_SELECT
|
|
2979
|
+
validation?: ContactFormFieldValidation;
|
|
2980
|
+
defaultValue?: string;
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
interface ContactFormPublic {
|
|
2984
|
+
id: string;
|
|
2985
|
+
key: string; // e.g. "main", "newsletter"
|
|
2986
|
+
name: string; // already localized \u2014 use as form heading
|
|
2987
|
+
description?: string; // already localized \u2014 use as subtitle
|
|
2988
|
+
submitButton: string; // already localized \u2014 use as submit label
|
|
2989
|
+
successMessage: string; // already localized \u2014 render after submit succeeds
|
|
2990
|
+
fields: ContactFormPublicField[]; // in display order; hidden fields already filtered out
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
interface ContactFormSummary {
|
|
2994
|
+
key: string;
|
|
2995
|
+
name: string;
|
|
2996
|
+
isDefault: boolean;
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
// Field-type \u2192 HTML mapping for dynamic rendering
|
|
3000
|
+
// ------------------------------------------------------------------
|
|
3001
|
+
// TEXT \u2192 <input type="text" ... pattern?={validation.pattern}>
|
|
3002
|
+
// TEXTAREA \u2192 <textarea rows={6} ...>
|
|
3003
|
+
// EMAIL \u2192 <input type="email" autoComplete="email" ...>
|
|
3004
|
+
// PHONE \u2192 <input type="tel" autoComplete="tel" ...>
|
|
3005
|
+
// NUMBER \u2192 <input type="number" min={validation.min} max={validation.max}>
|
|
3006
|
+
// URL \u2192 <input type="url" ...>
|
|
3007
|
+
// DATE \u2192 <input type="date" ...> (value is ISO yyyy-MM-dd)
|
|
3008
|
+
// SELECT \u2192 <select>{enumValues.map(...)}</select> \u2014 always rendered from enumValues
|
|
3009
|
+
// MULTI_SELECT \u2192 multiple <input type="checkbox">, value is string[]
|
|
3010
|
+
// CHECKBOX \u2192 single <input type="checkbox">, value is boolean
|
|
3011
|
+
// ------------------------------------------------------------------
|
|
3012
|
+
|
|
3013
|
+
// SDK methods
|
|
3014
|
+
// await brainerce.createInquiry(input) \u2192 POST /stores/{storeId}/inquiries
|
|
3015
|
+
// await brainerce.contactForms.list() \u2192 GET /stores/{storeId}/contact-forms
|
|
3016
|
+
// await brainerce.contactForms.get(key?, locale?) \u2192 GET /stores/{storeId}/contact-forms/{key}?locale={locale}
|
|
3017
|
+
//
|
|
3018
|
+
// Rules
|
|
3019
|
+
// - Rate limit: 3 submissions / 60s per IP \u2014 handle 429 responses gracefully
|
|
3020
|
+
// - Honeypot: always render an invisible field named \`honeypot\` and never send it
|
|
3021
|
+
// - Always pass \`locale\` \u2014 inbox filters inquiries by language, and schema labels come back translated
|
|
3022
|
+
// - Render \`schema.name\` / \`description\` / \`submitButton\` / \`successMessage\` directly \u2014 do NOT hardcode copy
|
|
3023
|
+
// - Unknown field keys are stripped server-side \u2014 safe to send extras during dev`;
|
|
2662
3024
|
var TYPES_BY_DOMAIN = {
|
|
2663
3025
|
products: PRODUCTS_TYPES,
|
|
2664
3026
|
cart: CART_TYPES,
|
|
@@ -2666,7 +3028,8 @@ var TYPES_BY_DOMAIN = {
|
|
|
2666
3028
|
orders: ORDERS_TYPES,
|
|
2667
3029
|
customers: CUSTOMERS_TYPES,
|
|
2668
3030
|
payments: PAYMENTS_TYPES,
|
|
2669
|
-
helpers: HELPERS_TYPES
|
|
3031
|
+
helpers: HELPERS_TYPES,
|
|
3032
|
+
inquiries: INQUIRIES_TYPES
|
|
2670
3033
|
};
|
|
2671
3034
|
function getTypesByDomain(domain) {
|
|
2672
3035
|
if (domain === "all") {
|
|
@@ -2687,7 +3050,17 @@ var AVAILABLE_DOMAINS = Object.keys(TYPES_BY_DOMAIN);
|
|
|
2687
3050
|
var GET_TYPE_DEFINITIONS_NAME = "get-type-definitions";
|
|
2688
3051
|
var GET_TYPE_DEFINITIONS_DESCRIPTION = "Get TypeScript type definitions from the Brainerce SDK, segmented by domain. Use this when you need to understand the exact shape of objects like Product, Cart, Checkout, Order, etc.";
|
|
2689
3052
|
var GET_TYPE_DEFINITIONS_SCHEMA = {
|
|
2690
|
-
domain: z2.enum([
|
|
3053
|
+
domain: z2.enum([
|
|
3054
|
+
"products",
|
|
3055
|
+
"cart",
|
|
3056
|
+
"checkout",
|
|
3057
|
+
"orders",
|
|
3058
|
+
"customers",
|
|
3059
|
+
"payments",
|
|
3060
|
+
"helpers",
|
|
3061
|
+
"inquiries",
|
|
3062
|
+
"all"
|
|
3063
|
+
]).describe(
|
|
2691
3064
|
'The domain of types to retrieve. Use "helpers" for helper function signatures and common types like StoreInfo, PaginatedResponse.'
|
|
2692
3065
|
)
|
|
2693
3066
|
};
|
package/package.json
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@brainerce/mcp-server",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "Framework-agnostic domain knowledge API for Brainerce. Provides SDK docs, types, business flows, critical rules, and store capabilities to AI tools like Lovable, Cursor, Bolt, v0, and Claude Code. Does NOT provide framework-specific boilerplate — clients build in whatever framework fits.",
|
|
5
|
-
"bin": {
|
|
6
|
-
"brainerce-mcp": "dist/bin/stdio.js"
|
|
7
|
-
},
|
|
8
|
-
"main": "dist/index.js",
|
|
9
|
-
"module": "dist/index.mjs",
|
|
10
|
-
"types": "dist/index.d.ts",
|
|
11
|
-
"exports": {
|
|
12
|
-
".": {
|
|
13
|
-
"types": "./dist/index.d.ts",
|
|
14
|
-
"require": "./dist/index.js",
|
|
15
|
-
"import": "./dist/index.mjs"
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
"files": [
|
|
19
|
-
"dist",
|
|
20
|
-
"README.md"
|
|
21
|
-
],
|
|
22
|
-
"scripts": {
|
|
23
|
-
"build": "tsup",
|
|
24
|
-
"dev": "tsup --watch",
|
|
25
|
-
"start:stdio": "node dist/bin/stdio.js",
|
|
26
|
-
"start:http": "node dist/bin/http.js",
|
|
27
|
-
"prepublishOnly": "npm run build"
|
|
28
|
-
},
|
|
29
|
-
"dependencies": {
|
|
30
|
-
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
31
|
-
"zod": "^3.24.0"
|
|
32
|
-
},
|
|
33
|
-
"devDependencies": {
|
|
34
|
-
"@types/node": "^22.0.0",
|
|
35
|
-
"tsup": "^8.0.0",
|
|
36
|
-
"typescript": "^5.3.0"
|
|
37
|
-
},
|
|
38
|
-
"engines": {
|
|
39
|
-
"node": ">=18"
|
|
40
|
-
},
|
|
41
|
-
"keywords": [
|
|
42
|
-
"brainerce",
|
|
43
|
-
"mcp",
|
|
44
|
-
"model-context-protocol",
|
|
45
|
-
"ai",
|
|
46
|
-
"ecommerce",
|
|
47
|
-
"lovable",
|
|
48
|
-
"cursor",
|
|
49
|
-
"claude-code",
|
|
50
|
-
"vibe-coding"
|
|
51
|
-
],
|
|
52
|
-
"license": "MIT"
|
|
53
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@brainerce/mcp-server",
|
|
3
|
+
"version": "3.4.0",
|
|
4
|
+
"description": "Framework-agnostic domain knowledge API for Brainerce. Provides SDK docs, types, business flows, critical rules, and store capabilities to AI tools like Lovable, Cursor, Bolt, v0, and Claude Code. Does NOT provide framework-specific boilerplate — clients build in whatever framework fits.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"brainerce-mcp": "dist/bin/stdio.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"module": "dist/index.mjs",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"require": "./dist/index.js",
|
|
15
|
+
"import": "./dist/index.mjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"dev": "tsup --watch",
|
|
25
|
+
"start:stdio": "node dist/bin/stdio.js",
|
|
26
|
+
"start:http": "node dist/bin/http.js",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
31
|
+
"zod": "^3.24.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^22.0.0",
|
|
35
|
+
"tsup": "^8.0.0",
|
|
36
|
+
"typescript": "^5.3.0"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"brainerce",
|
|
43
|
+
"mcp",
|
|
44
|
+
"model-context-protocol",
|
|
45
|
+
"ai",
|
|
46
|
+
"ecommerce",
|
|
47
|
+
"lovable",
|
|
48
|
+
"cursor",
|
|
49
|
+
"claude-code",
|
|
50
|
+
"vibe-coding"
|
|
51
|
+
],
|
|
52
|
+
"license": "MIT"
|
|
53
|
+
}
|