softr-vibe-coding 2.11.0 → 2.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/CHANGELOG.md +6 -0
- package/README.md +8 -2
- package/SKILL.md +5 -2
- package/package.json +1 -1
- package/references/anti-patterns.md +1 -1
- package/references/browser-checks.md +199 -0
- package/references/softr-mcp.md +31 -23
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to this skill are documented here. Versions follow [Semantic
|
|
|
4
4
|
|
|
5
5
|
Entries from 1.3.1 onward are generated automatically from git commit subjects between version bumps (see `.github/workflows/publish.yml`). Entries before 1.3.1 were backfilled by hand from the existing commit history.
|
|
6
6
|
|
|
7
|
+
## [2.12.0] - 2026-10-01
|
|
8
|
+
- Add references/browser-checks.md: checking a pushed block in a browser with agent-browser (verified 2026-10-01)
|
|
9
|
+
|
|
10
|
+
## [2.11.1] - 2026-10-01
|
|
11
|
+
- Correct four 2.11.0 statements after an independent fact-check of the transcripts
|
|
12
|
+
|
|
7
13
|
## [2.11.0] - 2026-10-01
|
|
8
14
|
- Track Softr's 2026-10-01 MCP release: renamed tools, sourceSha256 push checks, stub-tool root cause
|
|
9
15
|
|
package/README.md
CHANGED
|
@@ -195,8 +195,14 @@ softr-vibe-coding/
|
|
|
195
195
|
│ │ # enforces on block data endpoints, "Preview as"
|
|
196
196
|
│ │ # role testing, search-replace on 100KB+ blocks
|
|
197
197
|
│ │ # (Sep 18 2026); Oct 1 2026: tool rename map,
|
|
198
|
-
│ │ # push verification by sourceSha256, stub
|
|
199
|
-
│ │ #
|
|
198
|
+
│ │ # push verification by sourceSha256, stub tools
|
|
199
|
+
│ │ # after a resume, update_field/update_table fixes
|
|
200
|
+
│ ├── browser-checks.md # Checking a pushed block in a browser with
|
|
201
|
+
│ │ # the agent-browser CLI (ask before installing):
|
|
202
|
+
│ │ # preview cookie, shadow-DOM refs grepped in the
|
|
203
|
+
│ │ # shell, eval measurements, the records-trigger
|
|
204
|
+
│ │ # write guard proven before any click, what a
|
|
205
|
+
│ │ # click sent, screenshots to disk (Oct 1 2026)
|
|
200
206
|
│ ├── advanced-integrations.md # Shadow DOM CSS isolation
|
|
201
207
|
│ │ # Leaflet, Mapbox, TinyMCE, Quill, FullCalendar
|
|
202
208
|
│ ├── native-chrome-styling.md # Restyle Softr's native shell (header, footer,
|
package/SKILL.md
CHANGED
|
@@ -96,6 +96,7 @@ You generate complete, production-ready Softr Vibe Coding blocks as TypeScript R
|
|
|
96
96
|
- Array-setting rows keyed by **index**, never by a builder-editable field value
|
|
97
97
|
- Media settings that may start empty (`src: ""`) gated with a conditional render or placeholder — never an unconditional `<img src={setting.src}>`
|
|
98
98
|
- **Deploying through the MCP:** `errors: null` on a push is not proof — compare the push result's `sourceSha256` with `shasum -a 256` of the file you sent, every byte counted, trailing newline included (Softr stores exactly what it receives; the one-byte drift we once blamed on it was a chunked read on our side). Prove deployed == disk *before* editing the same way, with `vibe_coding_block_get_code` and `includeCode: false`, so a Studio-side change is never overwritten. Fetch the full `sourceCode` only when a digest is missing or the hashes differ. Protocol in [references/softr-mcp.md → Verifying a push](references/softr-mcp.md#verifying-a-push--the-deployed-source-is-the-only-proof)
|
|
99
|
+
- **Then check it in a browser.** Once the push checks pass, check rendering and behaviour in a fresh preview with saves blocked, per [references/browser-checks.md](references/browser-checks.md)
|
|
99
100
|
|
|
100
101
|
## What to Clarify
|
|
101
102
|
|
|
@@ -180,6 +181,7 @@ For advanced patterns beyond data fetching, load the relevant reference when the
|
|
|
180
181
|
| Small reusable patterns — `localStorage` cross-page state, clipboard copy button | [references/common-patterns.md](references/common-patterns.md) |
|
|
181
182
|
| Writing Airtable Automation Scripts / Scripting Extension scripts / Airtable formulas — companion to Softr blocks for cross-table cascades and computed values | [references/airtable-automations.md](references/airtable-automations.md) |
|
|
182
183
|
| The official **Softr MCP server** — Softr DB schema + full record/table/field/database CRUD (deletes included), field-level browsing of connected integrations (Airtable / Google Sheets / Notion / Supabase and more), **creating, editing, versioning, and deploying Vibe Coding blocks directly** (`vibe_coding_block_get_docs`, `vibe_coding_block_create`, ...), push verification by `sourceSha256`, app management/scaffolding, the **Softr Workflows** suite (28 tools, 418-node catalog), and **per-application MCP servers**. Tool names changed on 2026-10-01; the file carries the old → new map | [references/softr-mcp.md](references/softr-mcp.md) |
|
|
184
|
+
| **Checking a pushed block in a browser** — rendering and behaviour in a Softr preview with the agent-browser CLI (ask before installing it): the preview cookie, reaching into the block's shadow DOM through accessibility refs, measuring with `eval`, blocking and proving the save endpoint before any click, reading what a click sent, screenshots to disk | [references/browser-checks.md](references/browser-checks.md) |
|
|
183
185
|
| Restyling Softr's **native shell — header / footer / nav / dropdowns / page background** (not a block; it's Softr chrome, done with global Custom Code CSS): stable selectors vs. hashed classes, floating "island" header+footer, the dropdown blank-space grid fix, the multi-layer page-background stacking, restyle-vs-replace | [references/native-chrome-styling.md](references/native-chrome-styling.md) |
|
|
184
186
|
| Adding a **dynamic date filter or custom filter control to a native List/Grid block** (via a Custom Code Static block, not a Vibe block): drive the block's conditional filter with `{URL_PARAM:…}`, the empty-param "match nothing" wide-range sentinel, inject the control into the filter row and keep it alive across Softr's re-renders | [references/native-block-filters.md](references/native-block-filters.md) |
|
|
185
187
|
| **Editable settings deep-dive** — full hook catalog (incl. verified-undocumented `useLongTextSetting` and the `navigation` array-schema type), settings-first granularity doctrine, heading-line-split and `-text`/`-link` pairing patterns, naming conventions, rename-resets-value gotcha, empty-media gating, key-by-index rule | [references/editable-settings.md](references/editable-settings.md) |
|
|
@@ -570,8 +572,9 @@ Non-negotiable rules. Most are enforced by the Softr platform (compiler, validat
|
|
|
570
572
|
everyone can see it comes back `ALL_USERS`, i.e. writable by logged-OUT visitors (UPDATE/DELETE
|
|
571
573
|
reset to logged-in users). The MCP call that re-tightens it (`vibe_coding_block_set_action_visibility`)
|
|
572
574
|
can itself fail with no fallback. It fails when the client holds only a stub of the tool
|
|
573
|
-
(name-only description, no properties), which
|
|
574
|
-
loaded definition
|
|
575
|
+
(name-only description, no properties), which has happened after a session was resumed. Check
|
|
576
|
+
the loaded definition; if it is a stub, start a fresh top-level session (a subagent inherits the
|
|
577
|
+
stubs) (see
|
|
575
578
|
[references/softr-mcp.md](references/softr-mcp.md#the-array-argument-rejection-and-why-it-is-a-security-issue)).
|
|
576
579
|
A push that returns `errors: null` can still have left public write access on the block.
|
|
577
580
|
**If any action is still broader than intended, report it WITH its severity and let the builder decide.**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "softr-vibe-coding",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
4
4
|
"description": "Claude Code skill for generating production-ready Softr Vibe Coding blocks (JSX). Installs into ~/.claude/skills/ and auto-updates on each Claude Code session.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"softr-vibe-coding": "./bin/cli.js"
|
|
@@ -44,7 +44,7 @@ Run through this catalog before delivering any block. Every row is a violation o
|
|
|
44
44
|
| Tightening Actions-tab permissions before the block's final redeploy | Every code recompile **resets the auto-registered Actions to default permissions** (verified live 2026-08-25). Tighten permissions after the LAST redeploy, and re-check after any future one |
|
|
45
45
|
| Assuming a comment-only edit is "safe" and leaves Action permissions alone | There is no cosmetic-edit exemption. Any save recompiles, and every recompile rebuilds the Actions at default visibility — a `search_replace` changing nothing but a code comment resets them exactly like a rewrite (verified live 2026-09-09, on two blocks at once). Re-check after EVERY push, including cosmetic ones |
|
|
46
46
|
| Treating the permission-restore call as done because you issued it | Read the permissions back with `vibe_coding_block_get_settings` and confirm each one changed. `vibe_coding_block_set_action_visibility` can fail outright on the array-argument rejection (the client holding only a stub of the tool, typically after a session resume), and it has **no fallback** — the default for ADD_RECORD follows the block's own visibility, so on a block everyone can see, a routine push silently leaves it publicly writable while returning `errors: null`. Verified live 2026-09-09: four ADD_RECORD actions left open across two blocks. Report the list with its severity — check the page's VIEW permission with `application_page_get_permissions`, since a logged-in-gated page makes this housekeeping while a public page makes it a real hole — and let the builder decide whether it holds their release. A human sets them on the block's Actions tab |
|
|
47
|
-
| Calling an array-argument MCP tool (`vibe_coding_block_set_action_visibility`, `vibe_coding_block_update_code_search_replace`) when its loaded definition is a stub — description identical to the tool name, schema `{"type":"object"}` with no properties | A stub declares no types, so the array is sent as a JSON string and rejected (since 2026-10-01 the error reads "Parameter '…' must be an array of objects, but a string was sent"). Stubs
|
|
47
|
+
| Calling an array-argument MCP tool (`vibe_coding_block_set_action_visibility`, `vibe_coding_block_update_code_search_replace`) when its loaded definition is a stub — description identical to the tool name, schema `{"type":"object"}` with no properties | A stub declares no types, so the array is sent as a JSON string and rejected (since 2026-10-01 the error reads "Parameter '…' must be an array of objects, but a string was sent"). Stubs have appeared after a session was resumed, and subagents spawned from that session inherit them. Start a fresh top-level session, which reloaded the real definitions when we tried it, then call. Found 2026-10-01 — see [softr-mcp.md](softr-mcp.md#the-array-argument-rejection-and-why-it-is-a-security-issue) |
|
|
48
48
|
| Verifying a push by pulling the whole block source back into the model's context | Compare `sourceSha256` from the push result (or from `vibe_coding_block_get_code` with `includeCode: false`) with `shasum -a 256` of the file you sent; fetch the full source only when the digest is missing or differs. Available since 2026-10-01 — see [softr-mcp.md](softr-mcp.md#verifying-a-push--the-deployed-source-is-the-only-proof) |
|
|
49
49
|
| Splitting one table's writes across several `useRecordUpdate` hooks (or across two connections of the same table) to get separately-permissioned Actions | Actions register per **TABLE**: the hooks merge into ONE UPDATE_RECORD action whose field list is the union, and a hook pointed at a second connection of the table is still filed under the FIRST connection's dataSourceId (verified live 2026-09-18). Point writes at the table's first connection; expect one action per table + operation when re-tightening permissions. See [datasources/writing.md](../datasources/writing.md#actions-register-per-table-not-per-hook-or-connection) |
|
|
50
50
|
| Treating Studio's Actions tab as a separately-managed configuration to keep in sync with code | Actions auto-derive from your `useRecordCreate`/`useRecordUpdate`/`useRecordDelete` + `q.select` on every save. The Actions tab is a read-only inspector; there is no manual delete control. To change an Action, change the code |
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Checking a pushed block in a browser
|
|
2
|
+
|
|
3
|
+
How to check a deployed block's rendering and behaviour in a Softr preview with the
|
|
4
|
+
[agent-browser](https://github.com/vercel-labs/agent-browser) CLI. **Verified 2026-10-01** with
|
|
5
|
+
agent-browser v0.38.1 on macOS (Node 22) against a real Softr preview; only the commands under
|
|
6
|
+
[Untested but promising](#untested-but-promising) were not run.
|
|
7
|
+
|
|
8
|
+
## When to use it
|
|
9
|
+
|
|
10
|
+
After a push has passed the hash check in
|
|
11
|
+
[softr-mcp.md → Verifying a push](softr-mcp.md#verifying-a-push--the-deployed-source-is-the-only-proof).
|
|
12
|
+
The hash proves what Softr stored; a browser shows what it does: the layout at a given width, what
|
|
13
|
+
a control does, what a Save would send. **Not for data checks** (read records through the MCP or the
|
|
14
|
+
API), and **not for logged-in Studio or Airtable work**, which needs the user's own session and so
|
|
15
|
+
belongs to the user's own Chrome tool.
|
|
16
|
+
|
|
17
|
+
## Tool choice and why
|
|
18
|
+
|
|
19
|
+
**agent-browser**, because a check costs fewer tokens and can be made safe:
|
|
20
|
+
|
|
21
|
+
- One browser stays alive between shell commands (a daemon per `--session`), so a check is a few
|
|
22
|
+
short commands rather than one script.
|
|
23
|
+
- `eval` prints only its result. The Playwright MCP's `browser_run_code_unsafe` repeats the whole
|
|
24
|
+
script back in every result, under "### Ran Playwright code".
|
|
25
|
+
- Screenshots go to disk and cost nothing until someone opens one.
|
|
26
|
+
- It aborts requests by URL pattern and lists what a click sent: the write guard below.
|
|
27
|
+
|
|
28
|
+
An in-app or embedded browser pane stops rendering while it is hidden: IntersectionObserver and
|
|
29
|
+
`requestAnimationFrame` never fire, and screenshots time out. It can check scroll- or
|
|
30
|
+
visibility-driven behaviour only while it is visibly open.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
Check with `agent-browser --version`. If it is missing, **ask the user before installing**: it is a
|
|
35
|
+
global npm package plus a Chrome for Testing download (182 MB, about 360 MB on disk under
|
|
36
|
+
`~/.agent-browser`). On a yes:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm i -g agent-browser && agent-browser install
|
|
40
|
+
agent-browser doctor # passed every check; a headless launch took about 0.9 s
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
npm may warn `EBADENGINE`, asking for Node ≥ 24. It ran fine on Node 22: that requirement is for
|
|
44
|
+
building from source, and the CLI is a native binary. If the user declines, drive a headless Chrome
|
|
45
|
+
with Playwright, or use an in-app browser only while it is visibly open.
|
|
46
|
+
|
|
47
|
+
## Why not the skills.sh `agent-browser` skill
|
|
48
|
+
|
|
49
|
+
vercel-labs also publish an `agent-browser` skill on skills.sh. Do not install it. Its description
|
|
50
|
+
tells the agent to prefer it over every built-in browser tool, and it triggers on generic requests,
|
|
51
|
+
even Slack ones. Yet it is only a stub that loads `agent-browser skills get core` (about 38 KB, some
|
|
52
|
+
10k tokens). The CLI serves that guide on demand, matched to the installed version: run
|
|
53
|
+
`agent-browser skills get core` yourself, and only when this recipe is not enough.
|
|
54
|
+
|
|
55
|
+
## The recipe
|
|
56
|
+
|
|
57
|
+
### 1. Session, preview cookie, page
|
|
58
|
+
|
|
59
|
+
Work from a scratch directory, not the project: nothing is written to the working folder, and
|
|
60
|
+
screenshots go where you tell them.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
ab() { agent-browser --session softr-check "$@"; } # a function, not a variable: see Gotchas
|
|
64
|
+
ab open '<previewUrl>' >/dev/null # once per session: sets the preview cookie
|
|
65
|
+
ab set viewport 1280 900
|
|
66
|
+
ab open 'https://<subdomain>.preview.softr.app/<page>?recordId=<recordId>&autoUser=true'
|
|
67
|
+
ab wait --load networkidle # works on Softr previews
|
|
68
|
+
ab wait 2500 # 2000–3000 ms more, so the block's data hooks can load
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`<previewUrl>` is what the MCP's `application_preview` returns. `open` prints the URL it opened, and
|
|
72
|
+
this one carries a sign-in token, hence `/dev/null`. The direct URL then loads the app itself, not
|
|
73
|
+
the toolbar shell that frames it, so `document` in `eval` is the app's.
|
|
74
|
+
|
|
75
|
+
### 2. Reaching into the block
|
|
76
|
+
|
|
77
|
+
A block renders inside a shadow root, which CSS selectors and `find` locators do not cross.
|
|
78
|
+
**Refs from the accessibility readout do**: `ab snapshot -i` lists the block's textboxes, buttons
|
|
79
|
+
and checkboxes as `[ref=eN]`, and `ab fill @eN '…'` and `ab click @eN` act inside the block. Grep
|
|
80
|
+
the ref out in the same shell call, so the readout never enters your context:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
REF=$(ab snapshot -i | grep -o 'textbox "Search by[^[]*\[ref=e[0-9]*' | head -1 | grep -o 'e[0-9]*$'); ab fill "@$REF" 'term'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 3. Measuring with `eval`
|
|
87
|
+
|
|
88
|
+
`ab eval "<expr>"`, or `ab eval --stdin < check.js` for anything longer. An async IIFE is awaited,
|
|
89
|
+
and only the result is printed: return `JSON.stringify(...)` to get one JSON-encoded line. Find the
|
|
90
|
+
block's shadow root by text only that block contains:
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
(async () => {
|
|
94
|
+
const root = [...document.querySelectorAll('*')].map(e => e.shadowRoot).filter(Boolean)
|
|
95
|
+
.find(s => /<text unique to the block>/.test(s.textContent));
|
|
96
|
+
if (!root) return 'block not found';
|
|
97
|
+
const r = root.querySelector('<selector>').getBoundingClientRect();
|
|
98
|
+
return JSON.stringify({ left: r.left, right: innerWidth - r.right, top: r.top, width: r.width });
|
|
99
|
+
})()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
A date input has no ref (role `Date` in the full readout, absent from `-i`) and is React-controlled:
|
|
103
|
+
set it through the native setter, then fire both events, inside the IIFE once `root` is found.
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
const i = root.querySelector('input[type="date"]');
|
|
107
|
+
const set = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set;
|
|
108
|
+
set.call(i, '2026-10-15');
|
|
109
|
+
i.dispatchEvent(new Event('input', { bubbles: true }));
|
|
110
|
+
i.dispatchEvent(new Event('change', { bubbles: true }));
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 4. Block saves before any click, and prove it
|
|
114
|
+
|
|
115
|
+
The preview writes to the live data
|
|
116
|
+
([softr-mcp.md](softr-mcp.md#testing-as-any-app-user-without-logins--the-preview-as-switcher)), so
|
|
117
|
+
before a check clicks anything that could save:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
ab network route '*records-trigger*' --abort
|
|
121
|
+
ab eval "fetch('/records-trigger-probe-' + Date.now()).then(r => 'NOT BLOCKED ' + r.status).catch(e => 'blocked: ' + e.message)"
|
|
122
|
+
# blocked: Failed to fetch
|
|
123
|
+
ab eval "fetch('/plain-probe-' + Date.now()).then(r => 'reached ' + r.status).catch(e => 'blocked: ' + e.message)"
|
|
124
|
+
# reached 404 (the control: other requests still get through)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
A `useRecordUpdate` save goes out as a PATCH, with the record ID in the path (see Gotchas):
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
PATCH https://<subdomain>.preview.softr.app/v1/datasource/applications/<app>/pages/<page>/blocks/<block>/datasources/<ds>/records-trigger/<recordId>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Only this update endpoint is verified. Before clicking a create or delete control, learn its URL
|
|
134
|
+
with `ab network requests` where a write is harmless, never on client data, and block that pattern
|
|
135
|
+
too. Do not assume `*records-trigger*` covers it.
|
|
136
|
+
|
|
137
|
+
### 5. Click, then read what it sent
|
|
138
|
+
|
|
139
|
+
Read the record through the database API, click Save by its ref (step 2), then read the record
|
|
140
|
+
again: `updatedAt` and the field should be unchanged. The aborted request is still logged, so you
|
|
141
|
+
see the payload without it reaching the server:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
ab network requests --filter records-trigger # lists the save, with its id
|
|
145
|
+
ab network request <id> --json # method: PATCH, postData: {"context":{…},"fields":{"<fieldId>":"2026-10-15"}}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 6. Screenshots and cleanup
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
ab screenshot ./empty-1280.png # ✓ Screenshot saved to … (--full for the whole page)
|
|
152
|
+
ab network unroute
|
|
153
|
+
ab close # ✓ Browser closed (no process left behind)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Give a path; without one, it writes to a temp directory. A saved screenshot costs no tokens until
|
|
157
|
+
someone opens it; one shown inline costs about 1.5k. Left alone, the daemon exits after an hour idle.
|
|
158
|
+
|
|
159
|
+
## Gotchas
|
|
160
|
+
|
|
161
|
+
- **zsh does not word-split.** `AB="agent-browser --session x"; $AB open …` fails with "command not
|
|
162
|
+
found": use the function. Agent shells usually keep no functions or variables between calls, so
|
|
163
|
+
define `ab`, and grep any ref, in the call that uses them; the browser lives on in the daemon.
|
|
164
|
+
- **Selectors stop at the shadow root.** `ab fill 'input[placeholder^="…"]' 'x'` gives
|
|
165
|
+
`✗ Element not found`, and `find` locators fail the same way (upstream issue
|
|
166
|
+
vercel-labs/agent-browser#1266, open since April 2026). Use refs, or `eval`.
|
|
167
|
+
- **Refs change on every page load.** Grep again after each `open` or reload.
|
|
168
|
+
- **Readouts are huge on data pages.** A table-heavy page measured 268 KB in full, 197 KB with `-c`
|
|
169
|
+
and 164 KB with `-i` (or `-i -c`), about 40k tokens: `-i` keeps table cells as context for the
|
|
170
|
+
buttons in them. A small page was 5.8 KB, 2.9 KB with `-i`. Never print one on a data page: grep
|
|
171
|
+
it, or write it to a file.
|
|
172
|
+
- **Saves are PATCH, and the record ID is in the path.** A guard on POST misses them, and so does one
|
|
173
|
+
that looks for the record ID in the body; that one once let a write through. Match the URL, never
|
|
174
|
+
the method or the body.
|
|
175
|
+
- **Plain `ab network request <id>` printed only the URL** of the blocked save. Add `--json`.
|
|
176
|
+
- **A preview serves the version it was minted on.** After every push, mint a fresh one with
|
|
177
|
+
`application_preview` and open it again before checking anything.
|
|
178
|
+
- **The preview URL is a sign-in token.** Never share it ([why](softr-mcp.md#application-management-tools)).
|
|
179
|
+
- **Attachment URLs are re-signed on every read:** compare by id, filename and size, never by URL.
|
|
180
|
+
|
|
181
|
+
## Measured
|
|
182
|
+
|
|
183
|
+
The same check, an empty-state centring check at two widths plus a screenshot, cost about 650 tokens
|
|
184
|
+
with agent-browser (2026-10-01) against about 1,540 with the Playwright MCP's run-code
|
|
185
|
+
(2026-09-30): about 58% less, mostly because Playwright repeats the script back. Learning the tool
|
|
186
|
+
in a fresh session cost about 1.6k tokens, once, without loading `skills get core`.
|
|
187
|
+
|
|
188
|
+
## Untested but promising
|
|
189
|
+
|
|
190
|
+
In agent-browser's docs, **not yet tried against a Softr preview**. Try one before relying on it:
|
|
191
|
+
|
|
192
|
+
- `ab pdf <path>`: to check a print layout ([printing.md](printing.md#7-gotchas-and-testing) has
|
|
193
|
+
the verified Playwright route).
|
|
194
|
+
- `--init-script <path>` (before the first navigation) or `ab addinitscript <js>` (at runtime): for
|
|
195
|
+
example, to stub `window.print` before load.
|
|
196
|
+
- `ab screenshot --if-changed`: skips a screenshot that matches the last one.
|
|
197
|
+
- `ab diff snapshot`: compares the current readout with the last one.
|
|
198
|
+
- `ab a11y`: the built-in axe-core accessibility audit.
|
|
199
|
+
- `--allowed-domains <list>`: restricts the session's network to the domains listed.
|
package/references/softr-mcp.md
CHANGED
|
@@ -35,13 +35,13 @@ The official Softr MCP server (`https://mcp.softr.io/mcp`) gives an AI assistant
|
|
|
35
35
|
| Applications | **Create whole apps**; manage app users and login settings; swap an app's data source; read apps, pages, blocks, permissions, user groups; preview; publish — see [Application management tools](#application-management-tools) | https://docs.softr.io/mcp/apps |
|
|
36
36
|
| Vibe coding blocks | Create and edit blocks, manage settings, visibility, versions, data source connections | https://docs.softr.io/mcp/vibe-coding |
|
|
37
37
|
| Integrations | Browse external data sources connected to the workspace, down to field level | https://docs.softr.io/mcp/integrations |
|
|
38
|
-
| Workflows | Build, wire, test, and publish workflows —
|
|
38
|
+
| Workflows | Build, wire, test, and publish workflows — 28 tools and a 418-node trigger/action catalog; see [Workflows](#workflows) | https://docs.softr.io/mcp/workflows |
|
|
39
39
|
|
|
40
40
|
`workspace_list` is often the first call — it turns "my Sales workspace" into the workspace ID every other tool needs. The server's own instructions now start from `application_list` (applications and their workspace IDs) and `database_list` (databases), and keep `workspace_list` for turning a workspace name into an ID.
|
|
41
41
|
|
|
42
42
|
## Tool names — the 2026-10-01 rename
|
|
43
43
|
|
|
44
|
-
On 2026-10-01 Softr renamed
|
|
44
|
+
On 2026-10-01 Softr renamed the workspace-server tools outside Workflows to **area first, then verb**:
|
|
45
45
|
`vibe_coding_block_*`, `application_*` (pages are `application_page_*`), `database_*`,
|
|
46
46
|
`integration_*` and `workspace_*`. 79 tools were renamed and one was added
|
|
47
47
|
(`application_update_pwa_settings`); the 28 Workflows tools and `get_workspace_integrations` kept their names. The
|
|
@@ -264,9 +264,10 @@ unverified until the deployed source is proven identical to your file.
|
|
|
264
264
|
search-replace) and `vibe_coding_block_get_code` carry `sourceSha256` and `sourceBytes`: the SHA-256
|
|
265
265
|
of the UTF-8 source Softr persisted, and its length in bytes. A failed compile stores nothing and
|
|
266
266
|
reports neither field. With `includeCode: false`, `vibe_coding_block_get_code` returns the digest and
|
|
267
|
-
`sourceCode: null`. Verified live 2026-10-01: a deployed block's `sourceSha256` equalled
|
|
268
|
-
|
|
269
|
-
block.
|
|
267
|
+
`sourceCode: null`. Verified live 2026-10-01: a deployed block's `sourceSha256` equalled the SHA-256
|
|
268
|
+
of the exact source last pushed to it (taken from the push call), and the read came back at about
|
|
269
|
+
1 KB for a 15 KB block. The same check showed that block's local mirror had picked up three comment
|
|
270
|
+
edits since that push, which is exactly what step 1 below exists to catch. (The digest on push results is per Softr's release notes; no push of ours has shown it yet.)
|
|
270
271
|
Right after that release our client's copy of the tool definition did not declare `includeCode`, so
|
|
271
272
|
the argument went out as the string `"false"` and the server still honoured it. Whatever the loaded
|
|
272
273
|
definition says, check that `sourceCode` came back `null`.
|
|
@@ -292,8 +293,10 @@ definition says, check that `sourceCode` came back `null`.
|
|
|
292
293
|
result to a file rather than returning it inline, so compare from that file with a script, never
|
|
293
294
|
by eye.
|
|
294
295
|
|
|
296
|
+
Matching hashes prove what Softr stored, not how the block behaves: for that, check it in a fresh preview with saves blocked, per [browser-checks.md](browser-checks.md).
|
|
297
|
+
|
|
295
298
|
**Hash the exact bytes, trailing newline included.** Softr stores exactly what it receives: across
|
|
296
|
-
|
|
299
|
+
112 push→fetch pairs between 2026-09-09 and 2026-09-30 the fetched
|
|
297
300
|
`sourceCode` was byte- and MD5-identical to the text sent, including two pushes sent *without* a
|
|
298
301
|
final newline and stored without one. The "deployed block is one byte shorter" we chased on
|
|
299
302
|
2026-09-09 was our own read: an agent that reads a large file in chunks can drop the final
|
|
@@ -347,19 +350,23 @@ from String value (token `JsonToken.VALUE_STRING`)
|
|
|
347
350
|
| `vibe_coding_block_update_code_search_replace` | `operations` | Start a fresh session (below) | Use `vibe_coding_block_update_code` (full replace) |
|
|
348
351
|
| `vibe_coding_block_set_action_visibility` | `updates` | Start a fresh session (below) | **NONE — a human must fix it in Studio** |
|
|
349
352
|
|
|
350
|
-
**Where the string comes from
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
amount of care on the caller's side gets an array through a stub. The evidence, from the complete
|
|
357
|
-
transcripts of one build:
|
|
353
|
+
**Where the string comes from: tool stubs on the client side (found 2026-10-01).** In the sessions
|
|
354
|
+
we examined, our client (Claude Code) at times held the Softr tools as **stubs**: the description is
|
|
355
|
+
just the tool name and the input schema is `{"type":"object"}`, with no properties. A stub declares
|
|
356
|
+
no types, so an array argument goes out as a JSON string and Softr rejects it. Nothing is written,
|
|
357
|
+
so the failure is safe, but no amount of care on the caller's side gets an array through a stub.
|
|
358
|
+
The evidence, from the complete transcripts of one build:
|
|
358
359
|
|
|
359
360
|
- On 2026-09-09 every successful array call came before that session was resumed, and every
|
|
360
361
|
rejected one came after.
|
|
361
|
-
- On 2026-09-30,
|
|
362
|
-
|
|
362
|
+
- On 2026-09-30, after the session was picked up again, every Softr tool definition the client
|
|
363
|
+
recorded was a stub: in the session itself and in all 47 subagents it spawned. A live tool-list
|
|
364
|
+
update from the server that day did not change that.
|
|
365
|
+
- Only two things ever replaced the stubs with real definitions: re-adding the connector (once) and
|
|
366
|
+
a fresh session (2026-10-01).
|
|
367
|
+
- Not every pick-up produced stubs: a session picked up on the morning of 2026-09-09 held real
|
|
368
|
+
definitions. So the trigger is not fully understood. The stubs most likely come from our side
|
|
369
|
+
rather than Softr's server, since a fresh session got full definitions from the same server.
|
|
363
370
|
|
|
364
371
|
This replaces two earlier explanations in this file: that the model "had not loaded the tool
|
|
365
372
|
definitions" (2026-09-10), and that the workspace server's tools "can arrive schema-less"
|
|
@@ -370,7 +377,8 @@ publish full schemas.
|
|
|
370
377
|
|
|
371
378
|
**The rule:** before any array-argument call, look at the tool's loaded definition (ToolSearch shows
|
|
372
379
|
it). If the description is just the tool name and there are no `properties`, do not make the call:
|
|
373
|
-
start a fresh session first.
|
|
380
|
+
start a fresh top-level session first. A subagent is not a fresh session; it inherits the stubs.
|
|
381
|
+
That matters most for `vibe_coding_block_set_action_visibility`, which
|
|
374
382
|
has no fallback. For a code edit, a full replace is an acceptable stopgap: one file per subagent for
|
|
375
383
|
a large block, hash-verified.
|
|
376
384
|
|
|
@@ -378,8 +386,8 @@ a large block, hash-verified.
|
|
|
378
386
|
block's auto-registered Actions at Softr's default permissions. Per Softr (2026-10-01), the default
|
|
379
387
|
for **ADD_RECORD follows the block's own visibility**. On a block everyone can see, it comes back
|
|
380
388
|
`ALL_USERS`, writable by logged-OUT visitors. UPDATE_RECORD and DELETE_RECORD are always reset to
|
|
381
|
-
`LOGGED_IN_USERS`. That is what we saw on 2026-09-09, when one
|
|
382
|
-
open across two blocks. The remedy is to re-apply the permissions with
|
|
389
|
+
`LOGGED_IN_USERS`. That is what we saw on 2026-09-09, when one round of pushes (two saves, one per
|
|
390
|
+
block) left four ADD_RECORD actions open across two blocks and every restore call was rejected. The remedy is to re-apply the permissions with
|
|
383
391
|
`vibe_coding_block_set_action_visibility`. When that call is the one that fails, a routine cosmetic
|
|
384
392
|
push silently leaves public write access on the block. Nothing in the push result says so: the push
|
|
385
393
|
itself returns `errors: null, warnings: null`.
|
|
@@ -522,8 +530,8 @@ Combined with the database tools (`database_create` / `database_create_table` /
|
|
|
522
530
|
blocks yet. Softr has said placement will come later. Until then, a human drags it into place in
|
|
523
531
|
Studio. Say so when you hand the block over.
|
|
524
532
|
- **Timestamps are UTC with a `Z`.** Since 2026-10-01, timestamps such as `publishedAt` or a
|
|
525
|
-
version's `createdAt` are ISO-8601 UTC with millisecond precision
|
|
526
|
-
on `vibe_coding_block_list_versions` that day). Before then, studio-side timestamps came back with
|
|
533
|
+
version's `createdAt` are ISO-8601 UTC with millisecond precision, studio-side and tables-side
|
|
534
|
+
alike (per Softr; verified on `vibe_coding_block_list_versions` that day). Before then, studio-side timestamps came back with
|
|
527
535
|
no zone designator and nine fractional digits (`2026-09-09T22:34:11.157881061`). They were UTC, so
|
|
528
536
|
read any older logged value as UTC, never as local time.
|
|
529
537
|
|
|
@@ -602,7 +610,7 @@ For Softr's native databases the MCP goes far beyond browsing: `database_get_fie
|
|
|
602
610
|
|
|
603
611
|
**Call economy (from the server's own instructions):** `database_get_table` returns a table's metadata AND all its field definitions in one call; `database_list_fields` returns the fields alone. Call ONE of them once per table and reuse the result — never both — and re-fetch only after you changed the table's fields yourself.
|
|
604
612
|
|
|
605
|
-
**`database_get_field_reference` (was `get_schema`).** It describes the whole product, not one table: it takes no table ID and returns the same content every time. Live-confirmed 2026-08-31: `readOnlyFieldTypes` = AUTONUMBER, COUNT, CREATED_AT, CREATED_BY, FORMULA, LOOKUP, RECORD_ID, ROLLUP, UPDATED_AT, UPDATED_BY. On 2026-10-01 the list was the same without COUNT, which no longer appears in either the read-only list or the field types. The `LINKED_RECORD` value example is `["record-id-1", "record-id-2"]` — independently corroborating the verified string-array write shape in [../datasources/softr-database.md](../datasources/softr-database.md). On 2026-10-01 it listed `allowMultipleEntries` among the available options of SELECT and LINKED_RECORD. Operator families include relative-date `IS_WITHIN` / `IS_NOT_WITHIN` ("last 7 days"), ternary `IS_BETWEEN` / `IS_NOT_BETWEEN`, and `AND`/`OR` composites. **Schema-drift caution:** this reference and the [per-application servers'](#per-application-mcp-servers) `get_schema` have drifted. The per-app catalog lists creatable types the workspace one omits: ADDRESS, PROGRESS, TIME, DATE_RANGE and BUTTON were still absent from the workspace reference on 2026-10-01, although, per Softr, the workspace server returns fields of those types. The operator NAMES differed too (workspace `GREATER_THAN` / `DOES_NOT_CONTAIN` vs per-app `GT` / `DOES_NOT_CONTAINS`, observed 2026-08-31); per Softr the per-app names were realigned on 2026-09-09, which we have not re-checked. Do not assume a filter payload is portable between the two kinds: call the reference of the server you are actually using.
|
|
613
|
+
**`database_get_field_reference` (was `get_schema`).** It describes the whole product, not one table: it takes no table ID and returns the same content every time. Live-confirmed 2026-08-31 (and in reads of 2026-08-26 and 2026-09-09): `readOnlyFieldTypes` = AUTONUMBER, COUNT, CREATED_AT, CREATED_BY, FORMULA, LOOKUP, RECORD_ID, ROLLUP, UPDATED_AT, UPDATED_BY. On 2026-10-01 the list was the same without COUNT, which no longer appears in either the read-only list or the field types. The `LINKED_RECORD` value example is `["record-id-1", "record-id-2"]` — independently corroborating the verified string-array write shape in [../datasources/softr-database.md](../datasources/softr-database.md). On 2026-10-01 it listed `allowMultipleEntries` among the available options of SELECT and LINKED_RECORD. Operator families include relative-date `IS_WITHIN` / `IS_NOT_WITHIN` ("last 7 days"), ternary `IS_BETWEEN` / `IS_NOT_BETWEEN`, and `AND`/`OR` composites. **Schema-drift caution:** this reference and the [per-application servers'](#per-application-mcp-servers) `get_schema` have drifted. The per-app catalog lists creatable types the workspace one omits: ADDRESS, PROGRESS, TIME, DATE_RANGE and BUTTON were still absent from the workspace reference on 2026-10-01, although, per Softr, the workspace server returns fields of those types. The operator NAMES differed too (workspace `GREATER_THAN` / `DOES_NOT_CONTAIN` vs per-app `GT` / `DOES_NOT_CONTAINS`, observed 2026-08-31); per Softr the per-app names were realigned on 2026-09-09, which we have not re-checked. Do not assume a filter payload is portable between the two kinds: call the reference of the server you are actually using.
|
|
606
614
|
|
|
607
615
|
Known limits and behaviors (per official docs):
|
|
608
616
|
|
|
@@ -694,7 +702,7 @@ A separate product class from the workspace server (live-observed 2026-08-31 on
|
|
|
694
702
|
|
|
695
703
|
**Tools (12):** `list_tables`, `describe_table`, `get_schema`, `get_records`, `get_record`, `get_linked_records`, `get_current_user`, `create_record`, `update_record`, `delete_record`, `batch_update_records`, `batch_delete_records`. These are this server's own names, last enumerated by us in late August 2026; the 2026-10-01 rename of the workspace server did not touch them.
|
|
696
704
|
|
|
697
|
-
**If the tool definitions arrive empty, do not conclude the server sent them that way.** Every definition of these tools our client ever recorded had a name-only description and an `{"type":"object"}` schema, the same signature as the stubs described [above](#the-array-argument-rejection-and-why-it-is-a-security-issue). Unlike the workspace stubs, these were stubs even at times when the same client held real workspace definitions, so their cause is not settled. Per Softr, these servers publish full schemas and descriptions. A tool with no arguments (`list_tables`, `get_schema`, `get_current_user`) works either way; before relying on one that takes arguments, start a fresh session and check the loaded definition again.
|
|
705
|
+
**If the tool definitions arrive empty, do not conclude the server sent them that way.** Every definition of these tools our client ever recorded had a name-only description and an `{"type":"object"}` schema, the same signature as the stubs described [above](#the-array-argument-rejection-and-why-it-is-a-security-issue). Unlike the workspace stubs, these were stubs even at times when the same client held real workspace definitions, so their cause is not settled. Per Softr, these servers publish full schemas and descriptions. A tool with no arguments (`list_tables`, `get_schema`, `get_current_user`) works either way; before relying on one that takes arguments, start a fresh top-level session and check the loaded definition again.
|
|
698
706
|
|
|
699
707
|
**Live-observed semantics:**
|
|
700
708
|
|