@typeroll/mcp-server 0.11.0 → 0.12.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/AGENTS.md +12 -0
- package/dist/tools/block-types.js +10 -8
- package/package.json +1 -1
- package/skills/tr-imagegen.md +149 -0
package/AGENTS.md
CHANGED
|
@@ -84,6 +84,18 @@ maps to one HTTP endpoint; the actual logic runs in the customer's portal
|
|
|
84
84
|
before working with blocks — never hardcode block ids or field names,
|
|
85
85
|
the available set is per-site.
|
|
86
86
|
|
|
87
|
+
Two specifics worth knowing:
|
|
88
|
+
- **`core/html`** is the raw-HTML escape hatch for block-mode pages —
|
|
89
|
+
one `html` field rendered verbatim (then sanitized like HTML-mode
|
|
90
|
+
content). Use it for the genuinely unique thing no block covers: a
|
|
91
|
+
form embed with its signed `_token`, a third-party widget's markup,
|
|
92
|
+
a hand-crafted one-off. Prefer real blocks when one fits.
|
|
93
|
+
- **`script` on custom block types** (create/update_block_type) is
|
|
94
|
+
honoured only when the site has "Allow AI to write block scripts"
|
|
95
|
+
enabled — a human-set portal setting. When it's off your script is
|
|
96
|
+
stripped and the response carries a warning; relay it to the user
|
|
97
|
+
instead of retrying.
|
|
98
|
+
|
|
87
99
|
- **Redirects.** `from_path → to_path` with status code 301 / 302.
|
|
88
100
|
Auto-created when you change a page's slug.
|
|
89
101
|
|
|
@@ -84,15 +84,15 @@ export const blockTypeTools = [
|
|
|
84
84
|
}),
|
|
85
85
|
},
|
|
86
86
|
// ─── BlockType authoring — CREATE / UPDATE / DELETE ──────────────────
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
// distinguish these from human-authored types.
|
|
87
|
+
// `script` runs with full DOM access in every visitor's browser, so
|
|
88
|
+
// agent-authored scripts are gated server-side on a per-site human
|
|
89
|
+
// opt-in (Site.ai_scripts_enabled, set in the portal's settings UI).
|
|
90
|
+
// The tools expose the field; when the site hasn't opted in, the API
|
|
91
|
+
// strips it and returns a warning. Origin is stamped 'ai' so audit +
|
|
92
|
+
// UI distinguish these from human-authored types.
|
|
93
93
|
{
|
|
94
94
|
name: 'create_block_type',
|
|
95
|
-
description: "Create a new custom block type usable on this site. Origin is stamped 'ai' automatically. The block ships immediately to every page editor + the renderer's registry.
|
|
95
|
+
description: "Create a new custom block type usable on this site. Origin is stamped 'ai' automatically. The block ships immediately to every page editor + the renderer's registry. Custom JS via `script` requires the site's \"Allow AI to write block scripts\" setting (human-set in the portal) — when it's off, the field is ignored and the response carries a warning. Returns the created BlockType.",
|
|
96
96
|
inputSchema: {
|
|
97
97
|
name: z.string().describe('Machine name (lowercase kebab/underscore, 1-64 chars). Becomes the id.'),
|
|
98
98
|
label: z.string().optional().describe('Display label (defaults to name).'),
|
|
@@ -112,6 +112,7 @@ export const blockTypeTools = [
|
|
|
112
112
|
schema: z.array(z.unknown()).optional().describe('FieldDefinition[]. Each field has name, type, label, optional required/default/options/responsive.'),
|
|
113
113
|
template: z.string().optional().describe('HTML with {{field}}, {{{field}}}, {{=tag}}, {{children}}, {{slot:NAME}} substitutions.'),
|
|
114
114
|
styles: z.string().optional().describe('Block-scoped CSS. Selectors should start with [data-block="<name>"].'),
|
|
115
|
+
script: z.string().optional().describe('Client-side JS shipped with the block (runs on the published site). Only honoured when the site has enabled AI block scripts; otherwise stripped with a warning in the response.'),
|
|
115
116
|
version: versionParam,
|
|
116
117
|
},
|
|
117
118
|
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
@@ -126,7 +127,7 @@ export const blockTypeTools = [
|
|
|
126
127
|
},
|
|
127
128
|
{
|
|
128
129
|
name: 'update_block_type',
|
|
129
|
-
description: "Update fields of an existing custom block type. Core blocks (id starts with 'core/') are read-only — they're managed in platform code. Schema/template/styles replace wholesale (no deep merge); other fields shallow-merge.
|
|
130
|
+
description: "Update fields of an existing custom block type. Core blocks (id starts with 'core/') are read-only — they're managed in platform code. Schema/template/styles replace wholesale (no deep merge); other fields shallow-merge. The `script` field follows the same site-setting gate as create_block_type.",
|
|
130
131
|
inputSchema: {
|
|
131
132
|
type_id: z.string().describe('Block type id (custom or third_party). Cannot be a core/* block.'),
|
|
132
133
|
label: z.string().optional(),
|
|
@@ -143,6 +144,7 @@ export const blockTypeTools = [
|
|
|
143
144
|
schema: z.array(z.unknown()).optional(),
|
|
144
145
|
template: z.string().optional(),
|
|
145
146
|
styles: z.string().optional(),
|
|
147
|
+
script: z.string().optional().describe('Only honoured when the site has enabled AI block scripts; otherwise stripped with a warning.'),
|
|
146
148
|
version: versionParam,
|
|
147
149
|
},
|
|
148
150
|
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typeroll/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Model Context Protocol server for the Typeroll public API. Use with Claude Code or any MCP-compatible client to manage a Typeroll site.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tr-imagegen
|
|
3
|
+
description: Use when the user wants to generate images for a Typeroll site with AI models (Gemini, OpenAI, Higgsfield) — hero images, illustrations, section backgrounds, og-images. Triggers on "generera bilder", "generate images", "skapa en hero-bild", "AI-bilder", "bildgenerering", or when a brief calls for imagery that doesn't exist in assets/. Covers the local lab loop (generate → review → pick) and uploading winners to the Typeroll media library.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Generate images for a Typeroll site (local lab → media library)
|
|
7
|
+
|
|
8
|
+
Image generation runs **locally** in the site workdir — provider API keys
|
|
9
|
+
live in the folder's `.env`, candidates land in `images/lab/`, and only
|
|
10
|
+
the picked winners are uploaded to the Typeroll media library via the
|
|
11
|
+
regular media tools (see `tr-images` for the upload/variants half).
|
|
12
|
+
|
|
13
|
+
Why local: you can look at the candidates (Read the files), iterate on
|
|
14
|
+
prompts cheaply, and never ship a key or a reject anywhere.
|
|
15
|
+
|
|
16
|
+
## Folder convention
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
<site>/
|
|
20
|
+
├── .env # provider keys — GITIGNORED, never committed
|
|
21
|
+
├── prompts/
|
|
22
|
+
│ └── image-style.md # the site's image style profile (see below)
|
|
23
|
+
└── images/
|
|
24
|
+
└── lab/ # generated candidates — gitignored, disposable
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`.env` keys (only the ones the user has — check before assuming):
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
GEMINI_API_KEY=...
|
|
31
|
+
OPENAI_API_KEY=...
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
(Higgsfield needs no key here — it connects as an MCP server, see below.)
|
|
35
|
+
|
|
36
|
+
Load them per-command (`source .env` doesn't persist between Bash calls):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export $(grep -v '^#' .env | xargs) # prepend to each generation command
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## The style profile — prompts/image-style.md
|
|
43
|
+
|
|
44
|
+
Every site gets ONE style profile that you **prepend to every image
|
|
45
|
+
prompt**. This is what keeps 20 images generated across 5 sessions
|
|
46
|
+
looking like one site. Derive it from `assets/brand.md` + the brief if
|
|
47
|
+
it doesn't exist yet, and confirm it with the user before generating at
|
|
48
|
+
scale. Keep it short (5–10 lines): art direction, palette, mood,
|
|
49
|
+
photography vs illustration, what to avoid.
|
|
50
|
+
|
|
51
|
+
Example shape:
|
|
52
|
+
|
|
53
|
+
```markdown
|
|
54
|
+
# Bildstil — <Sajtnamn>
|
|
55
|
+
Varm, folklig illustration med mjuka rundade former. Platt 2D med
|
|
56
|
+
subtila skuggor — ingen 3D, ingen fotorealism. Palett: kobolt #1F4FB8,
|
|
57
|
+
sol #FFC83D, grädde #FFF8EC; accenter sparsamt. Människor: enkla,
|
|
58
|
+
inkluderande, glada — inga karikatyrer. Undvik: stockfoto-känsla, text
|
|
59
|
+
i bilden, logotyper, watermarks.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Generate candidates
|
|
63
|
+
|
|
64
|
+
Name candidates descriptively: `images/lab/<motiv>-<modell>-<n>.png`.
|
|
65
|
+
Generate 3–6 candidates per slot (mix models when several keys exist),
|
|
66
|
+
then **Read the files to actually look at them** before showing the
|
|
67
|
+
user your shortlist.
|
|
68
|
+
|
|
69
|
+
### Gemini (gemini-2.5-flash-image)
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
curl -s "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent" \
|
|
73
|
+
-H "x-goog-api-key: $GEMINI_API_KEY" -H 'Content-Type: application/json' \
|
|
74
|
+
-d '{
|
|
75
|
+
"contents": [{"parts": [{"text": "<STYLE PROFILE>\n\n<MOTIF PROMPT>"}]}],
|
|
76
|
+
"generationConfig": {"imageConfig": {"aspectRatio": "16:9"}}
|
|
77
|
+
}' | jq -r '.candidates[0].content.parts[] | select(.inlineData) | .inlineData.data' \
|
|
78
|
+
| base64 -d > images/lab/hero-gemini-1.png
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Aspect ratios: `1:1`, `16:9`, `4:3`, `3:4`, `9:16`. Gemini also does
|
|
82
|
+
image *editing* — pass an existing image as an `inlineData` part plus an
|
|
83
|
+
instruction to restyle/extend it (useful for "same illustration but
|
|
84
|
+
winter").
|
|
85
|
+
|
|
86
|
+
### OpenAI (gpt-image-1)
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
curl -s https://api.openai.com/v1/images/generations \
|
|
90
|
+
-H "Authorization: Bearer $OPENAI_API_KEY" -H 'Content-Type: application/json' \
|
|
91
|
+
-d '{
|
|
92
|
+
"model": "gpt-image-1",
|
|
93
|
+
"prompt": "<STYLE PROFILE>\n\n<MOTIF PROMPT>",
|
|
94
|
+
"size": "1536x1024",
|
|
95
|
+
"quality": "high"
|
|
96
|
+
}' | jq -r '.data[0].b64_json' | base64 -d > images/lab/hero-openai-1.png
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Sizes: `1024x1024`, `1536x1024` (landscape), `1024x1536` (portrait).
|
|
100
|
+
|
|
101
|
+
### Higgsfield (MCP server — no API key)
|
|
102
|
+
|
|
103
|
+
Higgsfield exposes its models through a hosted MCP server at
|
|
104
|
+
`https://mcp.higgsfield.ai/mcp` (OAuth-protected — first connect opens a
|
|
105
|
+
browser login; no secret lands in any file). Add it next to the
|
|
106
|
+
typeroll server in the site folder's `.mcp.json`:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
"higgsfield": { "type": "http", "url": "https://mcp.higgsfield.ai/mcp" }
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Then use its tools directly — list what's available rather than
|
|
113
|
+
assuming tool names. Save/download outputs into `images/lab/` with the
|
|
114
|
+
same naming convention, and prepend the style profile to prompts here
|
|
115
|
+
too. **Headless caveat:** OAuth-protected MCP servers need an existing
|
|
116
|
+
login session on the machine — connect once interactively before
|
|
117
|
+
relying on it in a headless run. (The same URL works as a custom
|
|
118
|
+
connector in Claude Desktop, for editors who don't use Claude Code.)
|
|
119
|
+
|
|
120
|
+
## Review → pick → upload
|
|
121
|
+
|
|
122
|
+
1. **Look at every candidate** (Read the image files) and write one line
|
|
123
|
+
per candidate in `build-log.md` (keep/reject + why).
|
|
124
|
+
2. Show the user the shortlist (file paths) and let them pick — unless
|
|
125
|
+
they've delegated the pick to you.
|
|
126
|
+
3. Upload winners with the regular media tools (see `tr-images`):
|
|
127
|
+
- small files → `upload_media_inline` (base64);
|
|
128
|
+
- larger → `create_upload_url` + HTTP PUT + `finalize_media`.
|
|
129
|
+
Give real `alt` text and a descriptive filename at upload time.
|
|
130
|
+
4. `generate_image_variants` for responsive sizes when the image is
|
|
131
|
+
placed full-width.
|
|
132
|
+
5. Place via `update_block` (`core/image`, hero fields, …) or page HTML.
|
|
133
|
+
6. `images/lab/` is disposable — leave rejects there; never upload them.
|
|
134
|
+
|
|
135
|
+
## Pitfalls
|
|
136
|
+
|
|
137
|
+
- **Never put text in generated images** (headlines, buttons) — text is
|
|
138
|
+
HTML's job; generated text renders as gibberish in non-English.
|
|
139
|
+
- **Don't commit `.env` or `images/lab/`** — both are gitignored by the
|
|
140
|
+
kit convention; keep it that way.
|
|
141
|
+
- **Cost discipline:** generation costs real money per image. Batch
|
|
142
|
+
thoughtfully (3–6 per slot, not 20), and reuse via Gemini's
|
|
143
|
+
edit-an-image mode instead of regenerating from scratch.
|
|
144
|
+
- **Licensing/provenance:** AI-generated imagery is fine for site
|
|
145
|
+
decoration, but never generate fake "photos" of real people, products
|
|
146
|
+
the customer doesn't sell, or anything presented as documentary fact.
|
|
147
|
+
- **The style profile is the contract.** If the user rejects a batch on
|
|
148
|
+
style grounds, fix `prompts/image-style.md` first, then regenerate —
|
|
149
|
+
don't just tweak one prompt.
|