@mindstudio-ai/remy 0.1.256 → 0.1.258

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.
@@ -0,0 +1,280 @@
1
+ ---
2
+ name: MCP Interfaces
3
+ what: Ships the app as an MCP server, so external AI agents — Claude Desktop, Cursor, anyone's agent — can drive it as a tool surface. The platform hosts the server, handles auth, and derives every tool's input schema from the method contract, so there is no protocol code to write: the work is choosing which methods an outsider should see and describing them well enough for a stranger to use correctly. Cheap to add to an app that already has methods, and it puts the app inside the tools its users already work in.
4
+ when: Before authoring `src/interfaces/mcp.md`, deciding which of the app's methods an external agent gets to see, or writing the MCP interface config.
5
+ ---
6
+
7
+ # MCP Interfaces
8
+
9
+ Exposing an app as an MCP server — a tool / resource / prompt surface for *external* AI agents (Claude
10
+ Desktop, Cursor, anyone's agent). Unlike the agent interface, which *is* an agent with its own LLM,
11
+ personality and chat UI, MCP has no model of its own: it's the app projected as a server for an outside
12
+ AI to drive.
13
+
14
+ It supports the full MCP surface:
15
+
16
+ - **Tools** — methods the agent can call (rich descriptions + machine-readable annotations).
17
+ - **Resources** — read-only app data the agent can pull into context, addressable by URI.
18
+ - **Prompts** — reusable, parameterized prompt templates the server offers.
19
+ - **Instructions** — server-level guidance shown to the calling agent (the toolset's "system prompt").
20
+
21
+ The platform hosts the server, handles auth, and derives every tool's input schema from the method
22
+ contract. So there's no protocol code to write, and the whole job is authorship plus a config file.
23
+ Authorship first, since that's what decides whether the toolset actually works.
24
+
25
+ ## The descriptions are the product
26
+
27
+ The calling agent is a stranger with no knowledge of your app. It decides what to invoke entirely from
28
+ the names, descriptions, and annotations you ship. Follow the same principles as the agent interface's
29
+ tool descriptions (load the `agentInterfaces` skill for those — when to use and when not, parameter
30
+ guidance beyond the schema, what the tool returns) — but write them **self-contained**. An in-app agent
31
+ tool can lean on the app's framing; an MCP tool can't, because the caller has no context. Spell out what
32
+ an outsider wouldn't know.
33
+
34
+ What that looks like in practice — the same method, described twice:
35
+
36
+ ```markdown
37
+ <!-- Weak: restates the schema, assumes the caller knows the app -->
38
+ Updates a vendor. Takes a vendorId and the fields to change.
39
+ ```
40
+
41
+ ```markdown
42
+ <!-- Strong: says when to call it, what an outsider can't infer, what comes back -->
43
+ Update an existing vendor's details. Call `listVendors` or `getVendor` first —
44
+ vendorId is the app's internal id, not a name, and there is no lookup by name.
45
+ Only the fields you pass are changed; omitted fields are left alone. Returns the
46
+ full updated vendor. Editors and admins only; other roles are rejected. For a
47
+ vendor that doesn't exist yet, use `createVendor`.
48
+ ```
49
+
50
+ The weak one is what a schema already tells the caller. The strong one carries the three things a schema
51
+ can't: the prerequisite, the partial-update semantics, and the alternative when this isn't the right
52
+ tool.
53
+
54
+ ## Curate — not every method is a tool
55
+
56
+ Expose what an outside agent would actually use. Skip internal helpers, admin-only methods, and batch
57
+ operations. A focused set of well-described tools beats a large set of thin ones. Note role restrictions
58
+ in the description — gated tools are listed but reject unauthorized calls at runtime, so set
59
+ expectations rather than surfacing a raw error.
60
+
61
+ ## Annotations
62
+
63
+ Annotations are machine-readable hints clients use to decide whether to auto-call a tool or ask the user
64
+ first. Set them honestly:
65
+
66
+ - `readOnly` — the tool only reads, never mutates. The highest-value hint: clients auto-call reads
67
+ without prompting, so set it on every pure read.
68
+ - `destructive` — the tool can delete or overwrite. Clients gate these behind confirmation.
69
+ - `idempotent` — calling twice with the same arguments has the same effect as calling once.
70
+ - `openWorld` — the tool reaches outside the app (external web/services) rather than operating only on
71
+ app data.
72
+
73
+ The judgement is per tool, and getting `readOnly` right is what makes a toolset feel responsive rather
74
+ than nagging. Abbreviated to just the annotations (a real entry also carries `name`, `title` and
75
+ `description` — the Config section has the full shape):
76
+
77
+ ```jsonc
78
+ "tools": [
79
+ { "method": "get-vendor", "annotations": { "readOnly": true, "idempotent": true } },
80
+ { "method": "search-vendors", "annotations": { "readOnly": true, "idempotent": true } },
81
+ { "method": "update-vendor", "annotations": { "idempotent": true } }, // repeatable, but it writes
82
+ { "method": "delete-vendor", "annotations": { "destructive": true, "idempotent": true } },
83
+ { "method": "enrich-vendor", "annotations": { "openWorld": true } } // calls out to the internet
84
+ ]
85
+ ```
86
+
87
+ Set them honestly rather than defensively. Marking a read `destructive` to be safe means the caller's
88
+ user gets a confirmation prompt for looking something up, and they will stop reading the prompts.
89
+
90
+ ## Tools vs. resources
91
+
92
+ A **tool** is an action the agent *invokes*; a **resource** is data the agent *reads into context*. A
93
+ read-only method can be either — expose it as a tool if the agent will call it as a step, as a resource
94
+ if it's reference data the agent should pull in, and as both when both fit.
95
+
96
+ Resources are method-backed: a read invokes the method. Use a static `uri` for a fixed collection
97
+ (`app://vendors`) and a `uriTemplate` when the read takes parameters (`app://vendors/{id}`, where `{id}`
98
+ maps to the method's input). Keep URIs stable and human-legible.
99
+
100
+ ## Prompts
101
+
102
+ Prompts are reusable, parameterized templates the server offers to clients — e.g. a "draft a vendor
103
+ email" starter. Author the template body with `{{arg}}` placeholders and declare its arguments. Offer a
104
+ prompt when there's a recurring task worth packaging; skip it if a tool already covers the need.
105
+
106
+ ## Server instructions
107
+
108
+ The spec's intro prose becomes the server `instructions` — toolset-level guidance returned to the
109
+ calling agent at connect time (its "system prompt"). Put *cross-cutting* guidance here: how the tools
110
+ fit together, ordering or prerequisites ("read a vendor before updating it"), and norms that apply
111
+ across the whole toolset. Keep per-tool specifics in the tool descriptions; instructions are for the
112
+ toolset as a whole.
113
+
114
+ ```markdown
115
+ This server exposes a procurement app. Vendors are the central record and
116
+ purchase orders reference them, so a vendor generally has to exist before
117
+ anything else is useful. Ids are internal — resolve a name to an id with a
118
+ search tool before calling anything that takes one. Search results are capped
119
+ at 50; page with the returned cursor rather than broadening the query.
120
+ ```
121
+
122
+ That's four sentences doing what no individual tool description could: it explains the shape of the
123
+ domain, so the calling agent's first move is a reasonable one.
124
+
125
+ ---
126
+
127
+ # The wiring
128
+
129
+ ## Spec: `src/interfaces/mcp.md`
130
+
131
+ Frontmatter declares the server. In the body, the intro prose becomes the server `instructions`, and
132
+ `## Tools`, `## Resources` and `## Prompts` headings declare the rest.
133
+
134
+ ```yaml
135
+ ---
136
+ name: Vendor Management
137
+ description: Tools and data for managing vendors and purchase orders.
138
+ type: interface/mcp
139
+ ---
140
+ ```
141
+
142
+ ```markdown
143
+ This server manages vendors and purchase orders. Read a vendor before updating it; submitted
144
+ requests go through approval before they become active.
145
+
146
+ ## Tools
147
+
148
+ ### Submit a vendor request
149
+ method: submit-vendor-request
150
+ ~~~
151
+ Submit a new vendor for approval. Use when the caller wants to add a vendor.
152
+ Do NOT use to modify an existing vendor — that's update-vendor.
153
+ - name: the vendor's legal name
154
+ - contactEmail: billing contact; required for approval routing
155
+ Returns the created vendor's id and its initial "pending" status.
156
+ ~~~
157
+
158
+ ### List vendors
159
+ method: list-vendors
160
+ annotations: readOnly
161
+ ~~~
162
+ List all vendors, newest first. Read-only.
163
+ ~~~
164
+
165
+ ## Resources
166
+
167
+ - list-vendors → app://vendors — "Vendors" — all vendors (application/json)
168
+ - get-vendor → app://vendors/{id} — "Vendor" — a single vendor by id (application/json)
169
+
170
+ ## Prompts
171
+
172
+ ### draft_vendor_email
173
+ description: Draft an outreach email to a vendor.
174
+ arguments: vendorId (required) — the vendor to contact
175
+ ~~~
176
+ Write a warm outreach email to vendor {{vendorId}} introducing our procurement process.
177
+ ~~~
178
+ ```
179
+
180
+ Don't hand-author input schemas — the platform derives them.
181
+
182
+ ## Compiled Output: `dist/interfaces/mcp/`
183
+
184
+ ```
185
+ dist/interfaces/mcp/
186
+ ├── interface.json ← config the platform reads
187
+ ├── instructions.md ← server-level guidance (returned in `initialize`)
188
+ ├── tools/
189
+ │ ├── submitVendorRequest.md ← rich description, one per tool
190
+ │ └── listVendors.md
191
+ └── prompts/
192
+ └── draftVendorEmail.md ← prompt template body, one per prompt
193
+ ```
194
+
195
+ Resources carry inline metadata only — no per-resource file.
196
+
197
+ ## Config (`interface.json`)
198
+
199
+ The top-level key must match the interface type (`mcp`):
200
+
201
+ ```json
202
+ {
203
+ "mcp": {
204
+ "name": "Vendor Management",
205
+ "description": "Tools and data for managing vendors and purchase orders.",
206
+ "instructions": "instructions.md",
207
+ "tools": [
208
+ {
209
+ "method": "submit-vendor-request",
210
+ "name": "submit_vendor_request",
211
+ "title": "Submit Vendor Request",
212
+ "description": "tools/submitVendorRequest.md",
213
+ "annotations": { "readOnly": false, "destructive": false, "idempotent": false, "openWorld": false }
214
+ },
215
+ {
216
+ "method": "list-vendors",
217
+ "title": "List Vendors",
218
+ "description": "tools/listVendors.md",
219
+ "annotations": { "readOnly": true }
220
+ }
221
+ ],
222
+ "resources": [
223
+ { "method": "list-vendors", "uri": "app://vendors", "name": "Vendors", "description": "All vendors.", "mimeType": "application/json" },
224
+ { "method": "get-vendor", "uriTemplate": "app://vendors/{id}", "name": "Vendor", "description": "A single vendor by id.", "mimeType": "application/json" }
225
+ ],
226
+ "prompts": [
227
+ {
228
+ "name": "draft_vendor_email",
229
+ "title": "Draft vendor email",
230
+ "description": "Draft an outreach email to a vendor.",
231
+ "arguments": [ { "name": "vendorId", "description": "The vendor to contact", "required": true } ],
232
+ "template": "prompts/draftVendorEmail.md"
233
+ }
234
+ ]
235
+ }
236
+ }
237
+ ```
238
+
239
+ | Field | Description |
240
+ |-------|-------------|
241
+ | `name`, `description` | Server display name + registry metadata (not shown to the calling agent) |
242
+ | `instructions` | Relative path to the server-level guidance returned in `initialize` |
243
+ | `tools[].method` | Method `id` from the manifest (kebab-case) |
244
+ | `tools[].name` | Tool name exposed to clients. Optional — defaults to the method `id`. Must match `[a-zA-Z0-9_-]` and be unique within the server |
245
+ | `tools[].title` | Optional human-friendly display name |
246
+ | `tools[].description` | Relative path to the tool's markdown description |
247
+ | `tools[].annotations` | The client hints from the Annotations section: `readOnly`, `destructive`, `idempotent`, `openWorld` — they map to MCP's `readOnlyHint` etc. |
248
+ | `resources[].method` | The read method invoked when the resource is read |
249
+ | `resources[].uri` / `uriTemplate` | A static URI, or a template whose `{param}` maps to the method's input |
250
+ | `resources[].name`, `description`, `mimeType` | Resource metadata |
251
+ | `prompts[].name`, `title`, `description` | Prompt identity + metadata |
252
+ | `prompts[].arguments` | `[{ name, description?, required? }]` |
253
+ | `prompts[].template` | Relative path to the template body (`{{arg}}` placeholders) |
254
+
255
+ There is no `inputSchema` field — the platform derives each tool's schema from the method's input
256
+ contract.
257
+
258
+ Declare it in `mindstudio.json`:
259
+
260
+ ```json
261
+ { "type": "mcp", "path": "dist/interfaces/mcp/interface.json" }
262
+ ```
263
+
264
+ ## Platform Behavior
265
+
266
+ - The platform hosts the MCP server and exposes it to external clients. Clients connect at
267
+ `POST https://{app-host}/_/mcp`, where `{app-host}` is any host the app is served on: its
268
+ `custom_subdomain` host (e.g. `myapp.madewithremy.com`), a custom domain if configured, or the UUID
269
+ host (`<appId>.madewithremy.com` / `.msagent.ai`).
270
+ - **Auth is optional.** A `Bearer` key resolves to a user with full RBAC, so the method's own
271
+ `auth.requireRole`/`requireUser` checks apply as they would for that user. With no key, calls run
272
+ anonymously — no user, no roles. The method is the boundary: gate sensitive tools, and understand that
273
+ a public (keyless) server effectively exposes only the un-gated ones.
274
+ - Input schemas are derived automatically from each method's input contract.
275
+ - `tools/list` is static; access is enforced per-method at call time (a gated tool is listed but rejects
276
+ an unauthorized call).
277
+ - A resource read invokes the backing method (template `{param}`s come from the URI) and returns its
278
+ output as the resource contents.
279
+ - `prompts/get` fills the template with the provided arguments.
280
+ - `instructions` is returned in the `initialize` response.
@@ -0,0 +1,149 @@
1
+ ---
2
+ name: REST API
3
+ what: A designed, documented REST surface over the app's methods — named routes, path and query params, resource groupings, and a generated OpenAPI spec. This is distinct from the method endpoints every app already has: those exist automatically and need nothing from you. This is for when the API itself is the product, or when something outside the app has to integrate against stable URLs rather than internal method names.
4
+ when: Before authoring `src/interfaces/api.md` or adding an `api` interface with designed routes. Also load it when a method needs the raw HTTP request (headers, unparsed body).
5
+ ---
6
+
7
+ # REST API Interfaces
8
+
9
+ REST endpoints for external consumers — other services, mobile apps, integrations. This is separate
10
+ from the web frontend's internal RPC (`@mindstudio-ai/interface` calls `/_/methods` directly and does
11
+ not use the API interface). The API interface lives at `/_/api/` and exposes only the methods you
12
+ choose to route.
13
+
14
+ Use it for sync endpoints for other services, a public REST API, batch tools — anything where
15
+ something outside the app's own frontend needs to call a method over HTTP.
16
+
17
+ **For provider webhooks (Stripe, GitHub, Shopify) there are two native paths**, and they are easy to
18
+ confuse. This interface handles them with bearer auth and `input._request.rawBody`. The Webhook
19
+ interface handles them with secret-in-URL routing and a top-level `input.rawBody` — usually the better
20
+ fit for provider callbacks, since providers can't send a bearer token. Load the `webhooks` skill before
21
+ choosing.
22
+
23
+ ## Spec: `src/interfaces/api.md`
24
+
25
+ The human-readable spec. Frontmatter declares the API name and description; the body maps methods to
26
+ REST routes using MSFM.
27
+
28
+ ```yaml
29
+ ---
30
+ name: Vendor Management API
31
+ description: API for managing vendors and purchase orders.
32
+ type: interface/api
33
+ ---
34
+ ```
35
+
36
+ Routes are declared as `VERB /path → methodExportName` under resource headings, with annotations for
37
+ params and descriptions:
38
+
39
+ ```markdown
40
+ ## Vendors
41
+
42
+ ### List vendors
43
+ GET /vendors → listVendors
44
+ ~~~
45
+ Returns all vendors, optionally filtered by status.
46
+ query: status (string, optional) — filter by vendor status
47
+ ~~~
48
+
49
+ ### Create vendor
50
+ POST /vendors → submitVendorRequest
51
+ ~~~
52
+ Submit a new vendor for approval.
53
+ body: name (string, required) — vendor name
54
+ contactEmail (string, required) — billing contact
55
+ ~~~
56
+
57
+ ### Delete vendor
58
+ DELETE /vendors/:vendorId → deleteVendor
59
+ ~~~
60
+ path: vendorId (string, required) — the vendor's unique identifier
61
+ ~~~
62
+ ```
63
+
64
+ ## Compiled Output: `dist/interfaces/api/api.json`
65
+
66
+ ```json
67
+ {
68
+ "api": {
69
+ "name": "Vendor Management API",
70
+ "description": "API for managing vendors and purchase orders.",
71
+ "routes": [
72
+ {
73
+ "method": "GET",
74
+ "path": "/vendors",
75
+ "handler": "list-vendors",
76
+ "summary": "List vendors",
77
+ "description": "Returns all vendors, optionally filtered by status.",
78
+ "tag": "Vendors",
79
+ "params": {
80
+ "query": {
81
+ "status": { "type": "string", "required": false, "description": "Filter by vendor status" }
82
+ }
83
+ }
84
+ },
85
+ {
86
+ "method": "POST",
87
+ "path": "/vendors",
88
+ "handler": "submit-vendor-request",
89
+ "summary": "Create vendor",
90
+ "description": "Submit a new vendor for approval.",
91
+ "tag": "Vendors",
92
+ "params": {
93
+ "body": {
94
+ "name": { "type": "string", "required": true, "description": "Vendor name" },
95
+ "contactEmail": { "type": "string", "required": true, "description": "Billing contact" }
96
+ }
97
+ }
98
+ },
99
+ {
100
+ "method": "DELETE",
101
+ "path": "/vendors/:vendorId",
102
+ "handler": "delete-vendor",
103
+ "summary": "Delete vendor",
104
+ "description": "Permanently remove a vendor.",
105
+ "tag": "Vendors",
106
+ "params": {
107
+ "path": {
108
+ "vendorId": { "type": "string", "required": true, "description": "The vendor's unique identifier" }
109
+ }
110
+ }
111
+ }
112
+ ]
113
+ }
114
+ }
115
+ ```
116
+
117
+ | Field | Description |
118
+ |-------|-------------|
119
+ | `name` | API display name (used in generated OpenAPI spec) |
120
+ | `description` | API description |
121
+ | `routes[].method` | HTTP method: `GET`, `POST`, `PUT`, `PATCH`, `DELETE` |
122
+ | `routes[].path` | URL path with `:param` placeholders for path params |
123
+ | `routes[].handler` | Method `id` from the manifest (kebab-case) |
124
+ | `routes[].summary` | Short description for the endpoint |
125
+ | `routes[].description` | Longer description |
126
+ | `routes[].tag` | Resource grouping (becomes a tag in OpenAPI) |
127
+ | `routes[].params` | Parameter declarations: `path`, `query`, and/or `body` objects |
128
+
129
+ Declare it in `mindstudio.json`:
130
+
131
+ ```json
132
+ { "type": "api", "path": "dist/interfaces/api/api.json" }
133
+ ```
134
+
135
+ ## Platform Behavior
136
+
137
+ Routes are mounted at `/_/api{path}` (e.g. `DELETE /_/api/vendors/abc123`).
138
+
139
+ - **Path params** are extracted and merged into the method's input: `/:vendorId` → `{ vendorId: "abc123" }`
140
+ - **Query params** are merged into input for GET requests: `?status=approved` → `{ status: "approved" }`
141
+ - **Request body** for POST/PUT/PATCH is the input directly (no `{ input: {...} }` wrapper)
142
+ - **Response** is the method output directly (no `{ output: {...} }` wrapper)
143
+ - **Auth** via `Authorization: Bearer sk_...` — an API key resolves to a user with full RBAC, so the
144
+ method's own `auth.requireRole`/`requireUser` checks apply exactly as they would for that user
145
+ - **Streaming**: `Accept: text/event-stream` header returns SSE chunks
146
+ - **Raw request context**: Every API method receives `input._request` with `{ method, headers, rawBody }`.
147
+ `rawBody` is the original unparsed body as a UTF-8 string — needed for signature verification, since
148
+ providers HMAC the raw payload and a re-serialized body won't match. For most methods you don't need
149
+ `_request` at all.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: Scheduled Jobs
3
+ what: Methods that run on a schedule, declared as cron expressions in an interface config and synced to the platform on deploy. Nothing to host and no scheduler to run — a job is a method plus a schedule line.
4
+ when: Before adding a `cron` interface or writing a method meant to run on a timer.
5
+ ---
6
+
7
+ # Cron Interfaces
8
+
9
+ Scheduled method execution.
10
+
11
+ ## Config (`interface.json`)
12
+
13
+ The top-level key must match the interface type (`cron`):
14
+
15
+ ```json
16
+ {
17
+ "cron": {
18
+ "jobs": [
19
+ {
20
+ "schedule": "0 9 * * 5",
21
+ "method": "process-weekly-payments",
22
+ "description": "Process approved invoices every Friday at 9am"
23
+ },
24
+ {
25
+ "schedule": "*/30 * * * *",
26
+ "method": "sync-vendor-status",
27
+ "description": "Sync vendor statuses every 30 minutes"
28
+ }
29
+ ]
30
+ }
31
+ }
32
+ ```
33
+
34
+ Standard cron expression format. `method` is the id of a method in `methods[]`. Jobs are synced to the
35
+ platform on deploy.
36
+
37
+ Declare it in `mindstudio.json`:
38
+
39
+ ```json
40
+ { "type": "cron", "path": "dist/interfaces/cron/interface.json" }
41
+ ```
42
+
43
+ ## Auth
44
+
45
+ Methods invoked through this interface run with `auth.roles: ['system']` — the platform is calling, not
46
+ a user session, so there's no user to impersonate. Use `auth.requireRole('system')` to gate methods that
47
+ should only be reachable on a schedule. The auth reference in your system prompt covers the system role
48
+ in full.
49
+
50
+ A scheduled job that needs to act on user data acts as the system, not as any user, so it reaches
51
+ everything. Scope what it touches in the method itself rather than relying on role checks to narrow it.
@@ -1,3 +1,9 @@
1
+ ---
2
+ name: Task Agents
3
+ what: A full autonomous agent loop callable from any method. Give it a prompt, a set of tools, and an example of the output shape; the platform runs the model until it produces that shape — searching, scraping, generating images, retrying approaches that failed, and calling your app's own methods to read and write data as it goes. Tools can be any of the 1000+ SDK actions and your own methods in any combination, which is what makes it part of the app rather than a detached research bot. This is the difference between a feature that saves what the user typed and one that researches, enriches, and creates on their behalf, and it is one of the most powerful things the platform can do. Consider it whenever a feature would be dramatically more compelling if the app could do real work autonomously.
4
+ when: Before writing any `mindstudio.runTask()` call — background enrichment, research-and-generate, anything where the model decides its own next step.
5
+ ---
6
+
1
7
  # Task Agents (`mindstudio.runTask`)
2
8
 
3
9
  A user types the name of a restaurant into your app, or uploads a photo of a storefront. The API call returns early, and in the background, a task agent searches Google, finds the official website, scrapes the address, gets the official social media accounts, and generates a stylized watercolor postcard of the exterior from images it found online. The user gets back a rich, illustrated card with the canonical name, website, address, and a custom image. A few tool calls (some in parallel), fully autonomous.
@@ -6,7 +12,7 @@ A user types the name of a restaurant into your app, or uploads a photo of a sto
6
12
 
7
13
  Tools are **SDK actions** (`searchGoogle`, `generateImage`, …) and **your own app's methods** (`{ appMethod: 'saveVendor' }`), in any combination. That second half is what makes a task agent part of your app rather than a detached research bot: it can read your tables to decide what to do next, and write results back itself instead of handing them to you to persist.
8
14
 
9
- This is one of the most powerful pieces of the MindStudio SDK and can make turn apps from amazing into truly magical. Use `askMindStudioSdk` to help construct the perfect agent for a task.
15
+ This is one of the most powerful pieces of the MindStudio SDK, and it can turn an app from amazing into truly magical. Use `askMindStudioSdk` to help construct the right agent for a task — including which model to give it.
10
16
 
11
17
  ## When to Use
12
18
 
@@ -62,7 +68,7 @@ const result = await mindstudio.runTask<{
62
68
  photoUrl: 'https://cdn.mindstudio.ai/...',
63
69
  },
64
70
 
65
- model: 'claude-5-sonnet',
71
+ model: 'claude-5-sonnet', // ask askMindStudioSdk — don't copy this one blind
66
72
  maxTurns: 15,
67
73
  });
68
74
 
@@ -136,7 +142,7 @@ Keep them short and task-specific. Say when to reach for it and when not to, sin
136
142
 
137
143
  ## Voice & Tone in Prompts
138
144
 
139
- When a task agent produces user-facing text, the prompt must include a note voice and tone constraints. Make sure to specify no emojis, em dashes, and other "ai-isms" in the prompt, as well as the desired tone and voice of the output.
145
+ When a task agent produces user-facing text, the prompt must state the voice and tone it should write in. Specify the desired voice explicitly, and rule out emojis, em dashes, and other "ai-isms" — the output goes straight to the user, so nothing downstream will catch them.
140
146
 
141
147
  ## Options
142
148
 
@@ -146,14 +152,10 @@ When a task agent produces user-facing text, the prompt must include a note voic
146
152
  | `input` | Yes | — | Structured input (passed as user message) |
147
153
  | `tools` | Yes | — | SDK action names and/or `{ appMethod, description }` entries, each with optional `defaults` |
148
154
  | `structuredOutputExample` | Yes | — | Object or JSON string showing expected output shape. Use realistic example values, not placeholders like `'string'` |
149
- | `model` | Yes | — | Model ID (must support tool use) |
155
+ | `model` | Yes | — | Model ID (must support tool use). Ask `askMindStudioSdk` for the right one — MindStudio's ids don't match vendor ids, so a plausible-looking guess is usually wrong |
150
156
  | `maxTurns` | No | 20 | Max loop iterations (capped at 100) |
151
157
  | `onEvent` | No | — | SSE event callback for real-time streaming |
152
158
 
153
- ## Models
154
-
155
- Use `askMindStudioSdk` for appropriate models given the task and its complexity.
156
-
157
159
  ## Return Value
158
160
 
159
161
  ```typescript
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: Webhooks
3
+ what: Inbound HTTP endpoints that run a method synchronously, routed by a secret in the URL rather than by an auth header — which is what makes them the right fit for provider callbacks from Stripe, GitHub, Shopify, Slack or Twilio, since those senders can't present a bearer token. Signature verification works natively off the raw request body.
4
+ when: Before adding a `webhook` interface or writing a method that receives a provider callback. Also load it before reaching for any confirmation-token, polling, or proxy workaround for inbound HTTP — those aren't needed here.
5
+ ---
6
+
7
+ # Webhook Interfaces
8
+
9
+ Inbound HTTP endpoints that invoke a method directly and synchronously — the caller waits for the
10
+ method to finish. Use for receiving webhooks from external services (Stripe, GitHub, Shopify, Slack,
11
+ Twilio). Direct inbound webhooks with signature verification work natively; do **not** build
12
+ confirmation-token or polling workarounds.
13
+
14
+ The other native path for inbound HTTP is the API interface, which uses bearer auth and exposes the raw
15
+ body at `input._request.rawBody` instead of at the top level. Use that one when the caller can send an
16
+ `Authorization` header and you want a documented REST surface; use this one for provider callbacks.
17
+ Load the `restApi` skill if that's the direction.
18
+
19
+ Webhook secrets are configured at the project level by the user through the Remy platform. Your job is
20
+ the `interface.json` and the handling method.
21
+
22
+ ## Config (`interface.json`)
23
+
24
+ The top-level key must match the interface type (`webhook`):
25
+
26
+ ```json
27
+ {
28
+ "webhook": {
29
+ "endpoints": [
30
+ {
31
+ "method": "handle-payment-webhook",
32
+ "secret": "whsec_pick_a_long_random_token",
33
+ "description": "Stripe events"
34
+ }
35
+ ]
36
+ }
37
+ }
38
+ ```
39
+
40
+ - `method` — the id of a method in `methods[]` to invoke.
41
+ - `secret` — a developer-chosen opaque token that is **both the routing key and the access guard**. It
42
+ is stable across deploys (compilation is a passthrough — redeploying never rotates it), so a URL you
43
+ register with Stripe/GitHub stays valid. Generate one long random value per endpoint and keep it
44
+ constant.
45
+ - Declare multiple endpoints if needed; each `secret` maps to one method.
46
+
47
+ Declare it in `mindstudio.json`:
48
+
49
+ ```json
50
+ { "type": "webhook", "path": "dist/interfaces/webhook/interface.json" }
51
+ ```
52
+
53
+ ## Endpoint URL
54
+
55
+ Register this with the external service:
56
+
57
+ ```
58
+ https://{app-host}/_/webhook/{secret}
59
+ ```
60
+
61
+ `{app-host}` is any host the app is served on: its `custom_subdomain` host (e.g.
62
+ `myapp.madewithremy.com`), a custom domain if configured, or the UUID host
63
+ (`<appId>.madewithremy.com` / `.msagent.ai`). All HTTP verbs are accepted.
64
+
65
+ ## Input
66
+
67
+ The method receives:
68
+
69
+ ```ts
70
+ {
71
+ method: string; // HTTP method
72
+ headers: Record<string, string>; // request headers
73
+ query: Record<string, string>; // query params
74
+ body: any; // parsed JSON / form body
75
+ rawBody: string; // exact raw request bytes (UTF-8), pre-parse
76
+ }
77
+ ```
78
+
79
+ For signature verification **always use `rawBody`, never `body`** — providers (Stripe, GitHub, Shopify,
80
+ Slack) HMAC the raw payload, and a re-serialized `body` will not match:
81
+
82
+ ```typescript
83
+ const event = stripe.webhooks.constructEvent(
84
+ input.rawBody,
85
+ input.headers['stripe-signature'],
86
+ process.env.STRIPE_WEBHOOK_SECRET!,
87
+ );
88
+ ```
89
+
90
+ `rawBody` is populated for `application/json` and `application/x-www-form-urlencoded` bodies (what these
91
+ providers send).
92
+
93
+ ## Response
94
+
95
+ Whatever the method returns as output is sent back to the caller as JSON; if it returns no output, the
96
+ platform responds `204`. A wrong/unknown secret returns `401`; an app with no live release returns
97
+ `404`.
98
+
99
+ ## Auth
100
+
101
+ Methods invoked through this interface run with `auth.roles: ['system']` — the platform is calling, not
102
+ a user session, so there's no user to impersonate. Use `auth.requireRole('system')` to gate methods that
103
+ should only be reachable via a platform trigger. The auth reference in your system prompt covers the
104
+ system role in full.
105
+
106
+ Note that the URL secret is the only access control on the endpoint itself, which is why it needs to be
107
+ long and random. Signature verification is a second, independent check that the payload really came from
108
+ the provider — do both.
@@ -19,7 +19,7 @@ The scaffold starts with these spec files that cover the full picture of the app
19
19
  - **`src/interfaces/@brand/voice.md`** — voice and terminology: tone, error messages, word choices
20
20
  - **`src/roadmap/`** — feature roadmap. One file per feature (`type: roadmap`). See "Roadmap" below.
21
21
 
22
- These are starting points, not constraints. Create as many spec files as the project needs — the `src/` folder is your workspace and every `.md` file in it becomes compilation context. If the app has substantial content (presentation slides, copy, lesson plans, menu items, quiz questions), put it in its own file (`src/content.md`, `src/slides.md`, `src/menu.md`, etc.) rather than cramming it into `app.md` or `web.md`. If the domain is complex, split `app.md` into multiple files by area (`src/billing.md`, `src/approvals.md`). Add interface specs for other interface types (`api.md`, `webhook.md`, `cron.md`, `agent.md`, etc.) if the app uses them. For external HTTP, the Webhook interface (`webhook.md`) handles inbound provider webhooks (Stripe, GitHub) via secret-in-URL routing, while the API interface (`api.md`) covers bearer-auth sync endpoints, public REST APIs, and batch tools. Organize however serves clarity — the platform reads the entire `src/` folder.
22
+ These are starting points, not constraints. Create as many spec files as the project needs — the `src/` folder is your workspace and every `.md` file in it becomes compilation context. If the app has substantial content (presentation slides, copy, lesson plans, menu items, quiz questions), put it in its own file (`src/content.md`, `src/slides.md`, `src/menu.md`, etc.) rather than cramming it into `app.md` or `web.md`. If the domain is complex, split `app.md` into multiple files by area (`src/billing.md`, `src/approvals.md`). Add interface specs for other interface types (`api.md`, `webhook.md`, `cron.md`, `email.md`, `mcp.md`, `agent.md`) if the app uses them. Each of those has a skill carrying its spec format and config — `restApi`, `webhooks`, `scheduledJobs`, `inboundEmail`, `mcpInterfaces`, `agentInterfaces` — and you should load the relevant one before writing the spec rather than after, since the spec is what the config is compiled from. For external HTTP the choice is between two of them: the Webhook interface handles inbound provider webhooks (Stripe, GitHub) via secret-in-URL routing, while the API interface covers bearer-auth sync endpoints, public REST APIs, and batch tools. Organize however serves clarity — the platform reads the entire `src/` folder.
23
23
 
24
24
  Remember: users care about look and feel as much as (and often more than) underlying data structures. Don't treat the brand and interface specs as an afterthought — for many users, the visual identity and voice are the first things they want to get right.
25
25
 
@@ -28,7 +28,7 @@ The user can already see your tool calls, so most of your work is visible withou
28
28
  Skip the rest: narrating what you're about to do, restating what the user asked, explaining tool calls they can already see.
29
29
 
30
30
  ### User attachments
31
- When a user uploads a file (PDF, Word doc, image, etc.), it is automatically saved to `src/.user-uploads/` in the project directory. The message includes the local file path, the CDN URL, and for documents with extractable text, a `.txt` sidecar with the extracted content. Use `readFile` on the sidecar to access document contents. The CDN URL can be used directly in code and specs without any upload step. If a raw file from `src/` needs to be served by the web interface, copy it to `dist/interfaces/web/public/`. These files persist across the conversation — they survive compaction and session restarts. Do not ask the user to re-upload a document that has already been saved. Voice messages are not saved to disk — their transcripts appear inline in the message.
31
+ When a user uploads a file (PDF, Word doc, image, etc.), it is automatically saved to `src/.user-uploads/` in the project directory. The message includes the local file path, and for documents with extractable text, a `.txt` sidecar with the extracted content. Use `readFile` on the sidecar to access document contents. Pass the file path itself to tools that take an image — `screenshot`, and the design expert's `analyzeImage` / `analyzeDesign` / `editImages` — and they host the file and hand back a URL you can reuse or embed in a spec. If a raw file from `src/` needs to be served by the web interface, copy it to `dist/interfaces/web/public/`. These files persist across the conversation — they survive compaction and session restarts. Do not ask the user to re-upload a document that has already been saved. Voice messages are not saved to disk — their transcripts appear inline in the message.
32
32
 
33
33
  ### Automated messages
34
34
  You will occasionally receive automated messages prefixed with `@@automated_message@@` - these are triggered by things like background agents returning their work, or by the user clicking a button in the UI (e.g., the user might click a "Build Feature" button in the product roadmap UI, and you will receive a message detailing what they want to build). You will be able to see these messages in your chat history but the user will not see them, so acknowledge them appropriately and then perform the requested work.