@typeroll/mcp-server 0.7.5 → 0.7.7
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/index.js +16 -1
- package/dist/install-skills.js +130 -0
- package/dist/tools/pages.js +11 -2
- package/dist/tools/partials.js +9 -4
- package/dist/tools/search.js +6 -3
- package/package.json +1 -1
- package/skills/README.md +23 -5
- package/skills/tr-blog.md +177 -0
- package/skills/tr-brand.md +169 -0
- package/skills/tr-forms.md +243 -0
- package/skills/tr-import-url.md +173 -0
- package/skills/tr-new-site.md +198 -0
- package/skills/tr-seo.md +179 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tr-forms
|
|
3
|
+
description: Use when the user wants to add a contact form, booking form, or any web form to their Typeroll site. Triggers on "lägg till formulär", "contact form", "add a form", "let visitors message us", "booking form", "formulär", or similar. Covers both creating the form definition and embedding the HTML widget on a page.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Add a form to a Typeroll site
|
|
7
|
+
|
|
8
|
+
Typeroll forms are server-backed: submissions go to
|
|
9
|
+
`/api/forms/submit` (HMAC-signed, rate-limited, honeypot-protected)
|
|
10
|
+
and are stored in Firestore. The user sees them in the portal's
|
|
11
|
+
Submissions inbox. No third-party service needed.
|
|
12
|
+
|
|
13
|
+
## Preconditions
|
|
14
|
+
|
|
15
|
+
- Site exists and MCP is configured.
|
|
16
|
+
- You know what fields the form needs.
|
|
17
|
+
|
|
18
|
+
## Recipe
|
|
19
|
+
|
|
20
|
+
### 1. Create the form definition
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
create_form {
|
|
24
|
+
"id": "kontakt",
|
|
25
|
+
"name": "Kontaktformulär",
|
|
26
|
+
"fields": [
|
|
27
|
+
{"name":"name", "label":"Namn", "type":"text", "required":true},
|
|
28
|
+
{"name":"email", "label":"E-post", "type":"email", "required":true},
|
|
29
|
+
{"name":"phone", "label":"Telefon", "type":"text"},
|
|
30
|
+
{"name":"message", "label":"Meddelande", "type":"textarea", "required":true},
|
|
31
|
+
{"name":"subject", "label":"Ämne", "type":"select",
|
|
32
|
+
"options":["Prisförfrågan","Samarbete","Övrigt"]}
|
|
33
|
+
],
|
|
34
|
+
"success_message": "Tack! Vi återkommer inom 24 timmar.",
|
|
35
|
+
"recipient_email": "hej@acme.se"
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Field types:** `text`, `email`, `tel`, `url`, `number`, `textarea`,
|
|
40
|
+
`select`, `radio`, `checkbox`.
|
|
41
|
+
|
|
42
|
+
**Field name rules:** Lowercase ASCII only: `[a-z][a-z0-9_-]*`.
|
|
43
|
+
- `besokt` not `besökt` (`ö→o`)
|
|
44
|
+
- `foretag` not `företag` (`ö→o`, `ä→a`)
|
|
45
|
+
- `meddelande` not `Meddelande` (the `name` must be lowercase; `label` can be anything)
|
|
46
|
+
|
|
47
|
+
### 2. Get the signed embed token
|
|
48
|
+
|
|
49
|
+
The form's submit button needs a signed token that proves it was
|
|
50
|
+
generated by the platform. Fetch it:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
read_form form_id="kontakt"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The response includes `submit_token` (a short-lived HMAC). Use this in
|
|
57
|
+
the HTML.
|
|
58
|
+
|
|
59
|
+
Actually, the embed HTML is best generated by reading the form and
|
|
60
|
+
building it manually — the form's `id` is all you need for the action URL.
|
|
61
|
+
|
|
62
|
+
### 3. Embed the form on a page
|
|
63
|
+
|
|
64
|
+
Typeroll forms submit to the platform's endpoint. The HTML:
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<section class="contact-section">
|
|
68
|
+
<div class="contact-container">
|
|
69
|
+
<h2>Kontakta oss</h2>
|
|
70
|
+
<p>Fyll i formuläret så återkommer vi inom 24 timmar.</p>
|
|
71
|
+
|
|
72
|
+
<form class="contact-form"
|
|
73
|
+
action="https://app.typeroll.com/api/forms/submit"
|
|
74
|
+
method="POST">
|
|
75
|
+
<!-- Hidden fields — required by the platform -->
|
|
76
|
+
<input type="hidden" name="_form_id" value="kontakt">
|
|
77
|
+
<input type="hidden" name="_site_id" value="YOUR_SITE_ID">
|
|
78
|
+
<input type="hidden" name="_token" value="SIGNED_TOKEN">
|
|
79
|
+
<!-- Honeypot — must stay empty, bots fill it -->
|
|
80
|
+
<input type="text" name="_hp" style="display:none" tabindex="-1" autocomplete="off">
|
|
81
|
+
|
|
82
|
+
<div class="form-group">
|
|
83
|
+
<label for="name">Namn *</label>
|
|
84
|
+
<input type="text" id="name" name="name" required>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div class="form-group">
|
|
88
|
+
<label for="email">E-post *</label>
|
|
89
|
+
<input type="email" id="email" name="email" required>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div class="form-group">
|
|
93
|
+
<label for="message">Meddelande *</label>
|
|
94
|
+
<textarea id="message" name="message" rows="5" required></textarea>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div class="form-group">
|
|
98
|
+
<label for="subject">Ämne</label>
|
|
99
|
+
<select id="subject" name="subject">
|
|
100
|
+
<option value="Prisförfrågan">Prisförfrågan</option>
|
|
101
|
+
<option value="Samarbete">Samarbete</option>
|
|
102
|
+
<option value="Övrigt">Övrigt</option>
|
|
103
|
+
</select>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<button type="submit" class="btn-primary">Skicka meddelande</button>
|
|
107
|
+
</form>
|
|
108
|
+
</div>
|
|
109
|
+
</section>
|
|
110
|
+
|
|
111
|
+
<style>
|
|
112
|
+
.contact-section{padding:4rem 2rem}
|
|
113
|
+
.contact-container{max-width:600px;margin:0 auto}
|
|
114
|
+
.form-group{margin-bottom:1.5rem}
|
|
115
|
+
.form-group label{display:block;font-weight:600;margin-bottom:0.4rem;font-size:0.9rem}
|
|
116
|
+
.form-group input,.form-group textarea,.form-group select{
|
|
117
|
+
width:100%;padding:0.75rem 1rem;border:1px solid var(--color-surface);
|
|
118
|
+
border-radius:0.375rem;font-family:inherit;font-size:1rem;
|
|
119
|
+
background:var(--color-surface);color:var(--color-text)
|
|
120
|
+
}
|
|
121
|
+
.form-group textarea{resize:vertical}
|
|
122
|
+
.btn-primary{
|
|
123
|
+
background:var(--color-primary);color:#fff;border:none;
|
|
124
|
+
padding:0.875rem 2rem;border-radius:0.375rem;font-size:1rem;
|
|
125
|
+
font-weight:600;cursor:pointer;width:100%
|
|
126
|
+
}
|
|
127
|
+
.btn-primary:hover{opacity:0.9}
|
|
128
|
+
</style>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Replace:**
|
|
132
|
+
- `YOUR_SITE_ID` → the site's actual id (from `get_site`)
|
|
133
|
+
- `SIGNED_TOKEN` → fetch via `read_form form_id="kontakt"` → `submit_token`
|
|
134
|
+
|
|
135
|
+
### 4. Add success/error handling (optional JS)
|
|
136
|
+
|
|
137
|
+
To show inline feedback without a page reload:
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<script>
|
|
141
|
+
(function(){
|
|
142
|
+
const form = document.querySelector('.contact-form');
|
|
143
|
+
if(!form) return;
|
|
144
|
+
form.addEventListener('submit', async(e) => {
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
const btn = form.querySelector('[type=submit]');
|
|
147
|
+
btn.disabled = true;
|
|
148
|
+
btn.textContent = 'Skickar…';
|
|
149
|
+
try {
|
|
150
|
+
const res = await fetch(form.action, {method:'POST', body: new FormData(form)});
|
|
151
|
+
const data = await res.json();
|
|
152
|
+
if(data.ok) {
|
|
153
|
+
form.innerHTML = '<p class="form-success">Tack! Vi återkommer inom 24 timmar.</p>';
|
|
154
|
+
} else {
|
|
155
|
+
btn.disabled = false;
|
|
156
|
+
btn.textContent = 'Skicka meddelande';
|
|
157
|
+
alert('Något gick fel: ' + (data.error || 'okänt fel'));
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
btn.disabled = false;
|
|
161
|
+
btn.textContent = 'Skicka meddelande';
|
|
162
|
+
alert('Nätverksfel — försök igen.');
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
})();
|
|
166
|
+
</script>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 5. Update the contact page with the form HTML
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
update_page page_id="kontakt" patch={
|
|
173
|
+
"html_content": "<full page HTML including the form section>"
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 6. Verify
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
read_form form_id="kontakt"
|
|
181
|
+
list_forms
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Confirm the form appears and fields match what you embedded.
|
|
185
|
+
|
|
186
|
+
### 7. Deploy
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
trigger_deploy
|
|
190
|
+
get_deploy_status job_id=<id>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
After deploy, test by submitting the live form. Submissions appear in the
|
|
194
|
+
portal at `/app/sites/{siteId}/forms/kontakt/submissions`.
|
|
195
|
+
|
|
196
|
+
## Common form patterns
|
|
197
|
+
|
|
198
|
+
### Booking / appointment request
|
|
199
|
+
```json
|
|
200
|
+
{"fields": [
|
|
201
|
+
{"name":"name", "type":"text", "label":"Namn", "required":true},
|
|
202
|
+
{"name":"email", "type":"email", "label":"E-post", "required":true},
|
|
203
|
+
{"name":"date", "type":"text", "label":"Önskat datum (YYYY-MM-DD)"},
|
|
204
|
+
{"name":"time", "type":"select","label":"Tid", "options":["09:00","10:00","11:00","14:00","15:00"]},
|
|
205
|
+
{"name":"notes", "type":"textarea","label":"Kommentar"}
|
|
206
|
+
]}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Newsletter signup (minimal)
|
|
210
|
+
```json
|
|
211
|
+
{"fields": [
|
|
212
|
+
{"name":"email", "type":"email", "label":"E-postadress", "required":true}
|
|
213
|
+
]}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Job application
|
|
217
|
+
```json
|
|
218
|
+
{"fields": [
|
|
219
|
+
{"name":"name", "type":"text", "label":"Namn", "required":true},
|
|
220
|
+
{"name":"email", "type":"email", "label":"E-post", "required":true},
|
|
221
|
+
{"name":"role", "type":"select","label":"Roll", "options":["Designer","Projektledare","Övrigt"]},
|
|
222
|
+
{"name":"experience", "type":"textarea","label":"Berätta om dig själv"},
|
|
223
|
+
{"name":"portfolio", "type":"url", "label":"Portfolio-URL"}
|
|
224
|
+
]}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Pitfalls
|
|
228
|
+
|
|
229
|
+
- **Tokens expire.** The `submit_token` in the form embed is short-lived
|
|
230
|
+
(24h by default). For forms on long-cached static pages, fetch a fresh
|
|
231
|
+
token via `read_form` and rebuild the page HTML, then redeploy.
|
|
232
|
+
Alternatively, fetch the token client-side via a small `<script>` that
|
|
233
|
+
calls the portal's token endpoint before submit.
|
|
234
|
+
- **Field names must be ASCII.** `message` not `meddelande`... wait,
|
|
235
|
+
`meddelande` is ASCII. `foretag` not `företag`. Only Swedish/Nordic
|
|
236
|
+
special characters (å, ä, ö) cause problems.
|
|
237
|
+
- **Don't use the same form_id on two different forms.** IDs must be
|
|
238
|
+
unique per site — use descriptive names: `kontakt`, `boka`, `nyhetsbrev`.
|
|
239
|
+
- **Honeypot must be invisible.** `_hp` field must have `display:none`.
|
|
240
|
+
If it's visible and a real user fills it, their submission is rejected.
|
|
241
|
+
- **Recipient email is display-only in phase 1.** The portal stores
|
|
242
|
+
submissions; email delivery to `recipient_email` depends on the platform's
|
|
243
|
+
email configuration. Confirm with the customer that they'll check the inbox.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tr-import-url
|
|
3
|
+
description: Use when the user wants to import or migrate content from a non-WordPress website — a Squarespace site, a Wix site, a static HTML site, a Webflow export, or any URL the user points at. Also triggers on "copy content from", "rebuild this site", "import from Squarespace/Wix/Webflow", or "make it look like this site". For WordPress sources use tr-migrate-wp instead.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Import content from a non-WordPress site
|
|
7
|
+
|
|
8
|
+
## When to use this vs tr-migrate-wp
|
|
9
|
+
|
|
10
|
+
| Source | Use |
|
|
11
|
+
|---|---|
|
|
12
|
+
| WordPress with `/wp-json` accessible | `tr-migrate-wp` |
|
|
13
|
+
| WordPress with REST disabled | This skill (scrape HTML) |
|
|
14
|
+
| Squarespace, Wix, Webflow, static HTML | This skill |
|
|
15
|
+
| CSV / spreadsheet data | This skill (skip scraping, just parse) |
|
|
16
|
+
| Any URL the user points at | This skill |
|
|
17
|
+
|
|
18
|
+
## Preconditions
|
|
19
|
+
|
|
20
|
+
- Target Typeroll site exists with working header/footer.
|
|
21
|
+
- Source URL(s) accessible (check with a quick `fetch`; if blocked, mention
|
|
22
|
+
it and ask the user for an HTML export or screenshot).
|
|
23
|
+
|
|
24
|
+
## Recipe
|
|
25
|
+
|
|
26
|
+
### 1. Inventory the source site
|
|
27
|
+
|
|
28
|
+
Fetch the homepage and build a URL list:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
fetch <source-url> # root HTML
|
|
32
|
+
fetch <source-url>/sitemap.xml # XML sitemap if it exists
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Parse `<a href>` links to discover internal pages. Build a list:
|
|
36
|
+
- Homepage
|
|
37
|
+
- Top-level pages (About, Services, Contact, etc.)
|
|
38
|
+
- Any sub-pages that look important
|
|
39
|
+
|
|
40
|
+
Avoid: pagination URLs, session URLs, `/wp-admin`, `/cdn-cgi/`, query strings.
|
|
41
|
+
|
|
42
|
+
### 2. Learn the target's design
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
read_site_settings
|
|
46
|
+
read_partial partial_id="header"
|
|
47
|
+
list_pages limit=5
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The goal is to understand what CSS variables, class names, and structural
|
|
51
|
+
conventions the target site uses so the imported content looks native.
|
|
52
|
+
|
|
53
|
+
### 3. Fetch and clean each source page
|
|
54
|
+
|
|
55
|
+
For each URL:
|
|
56
|
+
|
|
57
|
+
**a. Fetch the HTML.**
|
|
58
|
+
```
|
|
59
|
+
fetch <page-url>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
If the site returns a bot-block (Cloudflare, 403, or clearly JS-only
|
|
63
|
+
SPA output), note it. Tell the user: "This page blocked direct fetching.
|
|
64
|
+
Can you provide the page source or an HTML export?"
|
|
65
|
+
|
|
66
|
+
**b. Extract the main content.**
|
|
67
|
+
|
|
68
|
+
Discard: nav, header, footer, cookie banners, chat widgets, scripts.
|
|
69
|
+
Keep: `<main>`, `<article>`, the largest content region.
|
|
70
|
+
|
|
71
|
+
Clean the HTML:
|
|
72
|
+
- Strip platform-specific classes: `sqsrte-*`, `wf-*`, `et_*`,
|
|
73
|
+
`elementor-*`, `fl-*`, `divi-*`, `vc_*`
|
|
74
|
+
- Remove empty `<div>`, `<span>`, `<section>` wrappers (no class, no content)
|
|
75
|
+
- Unwrap redundant nesting: `<div><p>text</p></div>` → `<p>text</p>`
|
|
76
|
+
- Keep: `<h1>`–`<h6>`, `<p>`, `<ul>`, `<ol>`, `<img>`, `<a>`, `<table>`,
|
|
77
|
+
`<blockquote>`, `<figure>`, `<figcaption>`, `<strong>`, `<em>`
|
|
78
|
+
- Fix headings: ensure exactly one `<h1>` per page (the page title)
|
|
79
|
+
|
|
80
|
+
**c. Transfer images.** For each `<img src>`:
|
|
81
|
+
```
|
|
82
|
+
upload_media_from_url url="<source-img-url>" alt="..."
|
|
83
|
+
```
|
|
84
|
+
Replace the src with the returned CDN URL. Skip tracking pixels
|
|
85
|
+
(1×1 images), decorative SVGs that are just icons, and anything
|
|
86
|
+
that 404s.
|
|
87
|
+
|
|
88
|
+
**d. Adapt to the target's design.**
|
|
89
|
+
Replace source-specific CSS classes with target conventions.
|
|
90
|
+
Use `var(--color-*)` for colors, `var(--font-*)` for type.
|
|
91
|
+
|
|
92
|
+
### 4. Create pages as drafts
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
create_page title="Om oss" slug="om-oss"
|
|
96
|
+
html_content="<cleaned, adapted HTML>"
|
|
97
|
+
content_mode="html" status="draft"
|
|
98
|
+
seo_title="Om oss — Acme"
|
|
99
|
+
seo_description="..."
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Always draft first. The user signs off before publishing.
|
|
103
|
+
|
|
104
|
+
### 5. Handle redirects
|
|
105
|
+
|
|
106
|
+
If the source URLs differ from the target slugs, create redirects:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
create_redirect from_path="/about" to_path="/om-oss"
|
|
110
|
+
create_redirect from_path="/services.html" to_path="/tjanster"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 6. Preview with the user
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
get_preview_link
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Walk through every imported page with the user. Common issues:
|
|
120
|
+
- Heading hierarchy wrong (two H1s, or H3 used where H2 belongs)
|
|
121
|
+
- Images missing alt text
|
|
122
|
+
- Squarespace column layouts that don't work without their grid system
|
|
123
|
+
- Embedded forms or maps that need re-setup
|
|
124
|
+
|
|
125
|
+
### 7. Publish + deploy
|
|
126
|
+
|
|
127
|
+
After approval:
|
|
128
|
+
```
|
|
129
|
+
batch_update_pages updates=[
|
|
130
|
+
{page_id: "om-oss", patch: {status: "published"}},
|
|
131
|
+
{page_id: "tjanster", patch: {status: "published"}}
|
|
132
|
+
]
|
|
133
|
+
trigger_deploy
|
|
134
|
+
get_deploy_status job_id=<id>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Platform-specific notes
|
|
138
|
+
|
|
139
|
+
### Squarespace
|
|
140
|
+
- Main content is inside `.content-wrapper` or `[data-section-theme]` blocks
|
|
141
|
+
- Portfolio images are usually high-resolution originals in `/universal/images/`
|
|
142
|
+
- JSON-LD is Squarespace's own schema — strip it
|
|
143
|
+
- Gallery blocks → convert to CSS grid with inline `<img>` tags
|
|
144
|
+
|
|
145
|
+
### Wix
|
|
146
|
+
- Wix sites are React SPAs — `fetch` returns an empty shell
|
|
147
|
+
- Ask the user for the Wix site's "Export to HTML" (available in some plans)
|
|
148
|
+
or take screenshots for reference
|
|
149
|
+
- Best path: get content from the user (text + image files), rebuild clean
|
|
150
|
+
|
|
151
|
+
### Webflow
|
|
152
|
+
- Usually fetchable; clean output
|
|
153
|
+
- Classes like `w-container`, `w-row`, `w-col-*` can be stripped
|
|
154
|
+
- Webflow CMS items are server-rendered — they appear in the HTML
|
|
155
|
+
|
|
156
|
+
### Static HTML / old sites
|
|
157
|
+
- Often the cleanest import. Fetch, strip nav/footer, keep body.
|
|
158
|
+
- Watch for table-based layouts (pre-2010 sites) — convert to CSS grid
|
|
159
|
+
|
|
160
|
+
## Pitfalls
|
|
161
|
+
|
|
162
|
+
- **Don't import `<style>` blocks from the source site.** They reference
|
|
163
|
+
external fonts, resets, and classes that don't exist in the target.
|
|
164
|
+
Strip all `<style>` tags from source HTML and rewrite styles in the
|
|
165
|
+
target's conventions.
|
|
166
|
+
- **Don't break the single-H1 rule.** Many source sites have no H1 or
|
|
167
|
+
several. Fix it.
|
|
168
|
+
- **Squarespace/Wix forms.** They won't work after import — the backend
|
|
169
|
+
is vendor-locked. Create a Typeroll form instead: `create_form`.
|
|
170
|
+
- **Analytics/tracking code.** If the source has GA4 or similar, don't
|
|
171
|
+
copy it into pages. Set it via `update_site_settings scripts_head="..."`.
|
|
172
|
+
- **Videos.** YouTube/Vimeo embeds are fine (`<iframe>` is allowed).
|
|
173
|
+
Hosted MP4s need re-uploading if the source URL won't persist.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tr-new-site
|
|
3
|
+
description: Use when the user wants to create a new Typeroll site from scratch, set up the initial design, or bootstrap a blank site with working header/footer, brand colors, and a homepage. Also triggers on "start a new site", "set up a site for", or "build a website for [company]".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Bootstrap a new Typeroll site
|
|
7
|
+
|
|
8
|
+
Start here when the site already exists as a database record (created via
|
|
9
|
+
the portal UI or API) but has no design, no header/footer, and no pages.
|
|
10
|
+
The goal is to go from blank to a working 4-page site with correct brand
|
|
11
|
+
identity in a single session.
|
|
12
|
+
|
|
13
|
+
## Preconditions
|
|
14
|
+
|
|
15
|
+
- `@typeroll/mcp-server` configured with a valid `TYPEROLL_API_KEY`.
|
|
16
|
+
- The site exists (confirm with `get_site`).
|
|
17
|
+
- You have the customer brief: company name, industry, 2–3 key brand colors,
|
|
18
|
+
tone of voice (formal / friendly / minimal / vibrant), and a list of
|
|
19
|
+
initial pages.
|
|
20
|
+
|
|
21
|
+
## Recipe
|
|
22
|
+
|
|
23
|
+
### 1. Audit current state
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
get_site
|
|
27
|
+
read_site_settings # see what (if anything) is already configured
|
|
28
|
+
list_pages # don't overwrite pages that already exist
|
|
29
|
+
list_partials # check if header/footer already have content
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. Brand + settings
|
|
33
|
+
|
|
34
|
+
One `update_site_settings` call with every field you know:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"site_name": "Acme Studio",
|
|
39
|
+
"tagline": "Short, punchy tagline",
|
|
40
|
+
"language": "sv",
|
|
41
|
+
"colors": {
|
|
42
|
+
"primary": "#1a1a2e",
|
|
43
|
+
"secondary": "#16213e",
|
|
44
|
+
"accent": "#e94560",
|
|
45
|
+
"background": "#f5f5f5",
|
|
46
|
+
"surface": "#ffffff",
|
|
47
|
+
"text": "#1a1a2e",
|
|
48
|
+
"text_light": "#6b7280"
|
|
49
|
+
},
|
|
50
|
+
"fonts": {
|
|
51
|
+
"heading": "Playfair Display",
|
|
52
|
+
"body": "Inter",
|
|
53
|
+
"size_base": 16
|
|
54
|
+
},
|
|
55
|
+
"contact": {
|
|
56
|
+
"email": "hej@acme.se",
|
|
57
|
+
"phone": "+46 8 123 456",
|
|
58
|
+
"address": "Drottninggatan 1, 111 51 Stockholm"
|
|
59
|
+
},
|
|
60
|
+
"social": {
|
|
61
|
+
"instagram": "https://instagram.com/acme",
|
|
62
|
+
"linkedin": "https://linkedin.com/company/acme"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Read it back: `read_site_settings` — confirm `updated_fields` includes
|
|
68
|
+
everything you intended.
|
|
69
|
+
|
|
70
|
+
Google Fonts names: use the full display name as it appears on
|
|
71
|
+
fonts.google.com, e.g. "Cormorant Garamond", "DM Sans", "Plus Jakarta Sans".
|
|
72
|
+
|
|
73
|
+
### 3. Header partial
|
|
74
|
+
|
|
75
|
+
Replace the header with a real nav. Keep it simple:
|
|
76
|
+
|
|
77
|
+
```html
|
|
78
|
+
<header class="site-header">
|
|
79
|
+
<div class="header-inner">
|
|
80
|
+
<a class="header-logo" href="/">Acme Studio</a>
|
|
81
|
+
<nav class="header-nav">
|
|
82
|
+
<a href="/om-oss">Om oss</a>
|
|
83
|
+
<a href="/tjanster">Tjänster</a>
|
|
84
|
+
<a href="/kontakt">Kontakt</a>
|
|
85
|
+
</nav>
|
|
86
|
+
</div>
|
|
87
|
+
</header>
|
|
88
|
+
<style>
|
|
89
|
+
.site-header{background:var(--color-primary);padding:1rem 2rem}
|
|
90
|
+
.header-inner{max-width:1080px;margin:0 auto;display:flex;align-items:center;justify-content:space-between}
|
|
91
|
+
.header-logo{color:#fff;text-decoration:none;font-family:var(--font-heading);font-size:1.25rem;font-weight:700}
|
|
92
|
+
.header-nav{display:flex;gap:2rem}
|
|
93
|
+
.header-nav a{color:rgba(255,255,255,0.85);text-decoration:none;font-size:0.9rem;letter-spacing:0.05em;text-transform:uppercase}
|
|
94
|
+
.header-nav a:hover{color:#fff}
|
|
95
|
+
</style>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Use `replace_partial partial_id="header" html_content="..."`.
|
|
99
|
+
|
|
100
|
+
### 4. Footer partial
|
|
101
|
+
|
|
102
|
+
```html
|
|
103
|
+
<footer class="site-footer">
|
|
104
|
+
<div class="footer-inner">
|
|
105
|
+
<p class="footer-brand">Acme Studio</p>
|
|
106
|
+
<p class="footer-tagline">Tagline here</p>
|
|
107
|
+
<div class="footer-links">
|
|
108
|
+
<a href="/om-oss">Om oss</a>
|
|
109
|
+
<a href="/kontakt">Kontakt</a>
|
|
110
|
+
</div>
|
|
111
|
+
<p class="footer-copy">© 2025 Acme Studio. Alla rättigheter förbehållna.</p>
|
|
112
|
+
</div>
|
|
113
|
+
</footer>
|
|
114
|
+
<style>
|
|
115
|
+
.site-footer{background:var(--color-secondary);color:rgba(255,255,255,0.7);padding:3rem 2rem;margin-top:4rem}
|
|
116
|
+
.footer-inner{max-width:1080px;margin:0 auto;text-align:center;display:grid;gap:1rem}
|
|
117
|
+
.footer-brand{font-family:var(--font-heading);font-size:1.5rem;color:#fff;font-weight:700}
|
|
118
|
+
.footer-links{display:flex;justify-content:center;gap:2rem}
|
|
119
|
+
.footer-links a{color:rgba(255,255,255,0.7);text-decoration:none}
|
|
120
|
+
.footer-links a:hover{color:#fff}
|
|
121
|
+
.footer-copy{font-size:0.8rem;opacity:0.5}
|
|
122
|
+
</style>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Use `replace_partial partial_id="footer" html_content="..."`.
|
|
126
|
+
|
|
127
|
+
### 5. Homepage
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
create_page title="Start" slug="" content_mode="html"
|
|
131
|
+
html_content="<full hero + intro HTML>"
|
|
132
|
+
status="published"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`slug=""` creates the homepage under `page_id: "home"`. If it already
|
|
136
|
+
exists, use `update_page page_id="home" patch={html_content: "..."}`.
|
|
137
|
+
|
|
138
|
+
A minimal homepage structure:
|
|
139
|
+
- Hero: full-width section with `<h1>`, tagline, one CTA button
|
|
140
|
+
- Intro: 2–3 sentences about what the company does
|
|
141
|
+
- Services/features grid: 3 cards max
|
|
142
|
+
- CTA band: "Kontakta oss" or similar
|
|
143
|
+
|
|
144
|
+
### 6. Inner pages
|
|
145
|
+
|
|
146
|
+
Create each page:
|
|
147
|
+
```
|
|
148
|
+
create_page title="Om oss" slug="om-oss" content_mode="html"
|
|
149
|
+
html_content="..." status="published"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Standard set: Om oss, Tjänster, Kontakt. Add more if the brief says so.
|
|
153
|
+
|
|
154
|
+
### 7. Preview + iterate
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
get_preview_link # get a signed URL for the whole site
|
|
158
|
+
get_page_preview page_id="home" # get rendered HTML to spot-check
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Share the preview link with the user. Iterate on feedback.
|
|
162
|
+
|
|
163
|
+
### 8. Deploy
|
|
164
|
+
|
|
165
|
+
When the user approves:
|
|
166
|
+
```
|
|
167
|
+
trigger_deploy
|
|
168
|
+
get_deploy_status job_id=<id> # poll until status=succeeded
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Design conventions
|
|
172
|
+
|
|
173
|
+
- **One `<h1>` per page.** SEO and screen reader requirement.
|
|
174
|
+
- **CSS variables for colors.** Always use `var(--color-primary)` etc. in
|
|
175
|
+
partial `<style>` blocks — never hardcode hex values.
|
|
176
|
+
- **Responsive layout.** Use `max-width` on containers, `display:flex`
|
|
177
|
+
or `display:grid` for layouts, and `clamp()` for fluid type.
|
|
178
|
+
- **Scoped styles.** Put `<style>` at the bottom of partial HTML.
|
|
179
|
+
Styles apply to the whole page; prefix class names with the partial ID
|
|
180
|
+
to avoid collisions (`site-header__*`, `site-footer__*`).
|
|
181
|
+
- **Minimal JS.** The platform doesn't bundle JS. If you need interactions,
|
|
182
|
+
write a small `<script>` inside the partial or page; keep it under 50
|
|
183
|
+
lines. Avoid external CDN dependencies in page bodies.
|
|
184
|
+
|
|
185
|
+
## Pitfalls
|
|
186
|
+
|
|
187
|
+
- **Don't create pages that already exist.** Run `list_pages` first;
|
|
188
|
+
if the slug is taken, use `update_page` instead of `create_page`.
|
|
189
|
+
- **Don't hardcode text from settings into partials.** If `site_name`
|
|
190
|
+
is "Acme", the header partial should have literal "Acme" in it —
|
|
191
|
+
there's no template engine. When the name changes the user updates
|
|
192
|
+
the partial.
|
|
193
|
+
- **Google Fonts names are case-sensitive.** "Playfair Display" works;
|
|
194
|
+
"playfair display" or "Playfair-Display" do not.
|
|
195
|
+
- **Empty `custom_css` field.** If you set `custom_css: ""` it's
|
|
196
|
+
stored as an empty string, not removed. Use it only when you have
|
|
197
|
+
global styles that span both partials and pages (e.g. utility classes,
|
|
198
|
+
`@keyframes`, `:root` overrides for things not in `colors`/`fonts`).
|