@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.
- package/dist/headless.js +514 -315
- package/dist/index.js +517 -288
- package/dist/prompt/compiled/files.md +17 -3
- package/dist/prompt/compiled/interfaces.md +26 -472
- package/dist/prompt/compiled/methods.md +10 -15
- package/dist/prompt/compiled/scenarios.md +16 -0
- package/dist/prompt/compiled/sdk-actions.md +1 -2
- package/dist/prompt/{compiled/agent-interfaces.md → skills/agentInterfaces.md} +118 -10
- package/dist/prompt/skills/dataSources.md +131 -0
- package/dist/prompt/skills/inboundEmail.md +116 -0
- package/dist/prompt/skills/mcpInterfaces.md +280 -0
- package/dist/prompt/skills/restApi.md +149 -0
- package/dist/prompt/skills/scheduledJobs.md +51 -0
- package/dist/prompt/{compiled/task-agents.md → skills/taskAgents.md} +10 -8
- package/dist/prompt/skills/webhooks.md +108 -0
- package/dist/prompt/static/authoring.md +1 -1
- package/dist/prompt/static/instructions.md +1 -1
- package/dist/subagents/designExpert/prompts/images.md +1 -1
- package/package.json +2 -2
- package/dist/prompt/.notes.md +0 -194
- package/dist/prompt/compiled/README.md +0 -100
- package/dist/prompt/compiled/mcp-interfaces.md +0 -34
- package/dist/prompt/compiled/media-cdn.md +0 -51
- package/dist/prompt/sources/llms.txt +0 -1618
- package/dist/subagents/.notes-background-agents.md +0 -64
- package/dist/subagents/codeSanityCheck/.notes.md +0 -44
- package/dist/subagents/designExpert/.notes.md +0 -265
- package/dist/subagents/productVision/.notes.md +0 -79
|
@@ -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
|
|
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
|
|
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`, `
|
|
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,
|
|
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.
|