@mindstudio-ai/remy 0.1.257 → 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/prompt/compiled/interfaces.md +19 -464
- package/dist/prompt/compiled/methods.md +6 -10
- package/dist/prompt/skills/agentInterfaces.md +91 -2
- package/dist/prompt/skills/inboundEmail.md +116 -0
- package/dist/prompt/skills/mcpInterfaces.md +221 -21
- package/dist/prompt/skills/restApi.md +149 -0
- package/dist/prompt/skills/scheduledJobs.md +51 -0
- package/dist/prompt/skills/webhooks.md +108 -0
- package/dist/prompt/static/authoring.md +1 -1
- package/package.json +1 -1
|
@@ -4,6 +4,8 @@ Interfaces are projections of the backend contract into different modalities. Th
|
|
|
4
4
|
|
|
5
5
|
All external service connections (webhook secrets, email addresses) are configured at the project level by the user through the Remy platform. The agent's job is to write the config files and the methods that handle the requests — not to manage API keys, OAuth flows, or service registration.
|
|
6
6
|
|
|
7
|
+
`{app-host}`, where it appears in a URL below, means any host the app is served on: its `custom_subdomain` host (e.g. `myapp.madewithremy.com`), a custom domain if one is configured, or the UUID host (`<appId>.madewithremy.com` / `.msagent.ai`).
|
|
8
|
+
|
|
7
9
|
## Web Interface
|
|
8
10
|
|
|
9
11
|
A full web application — typically Vite + React, but any framework that produces static output works.
|
|
@@ -84,7 +86,7 @@ auth.verifySmsCode(verId, code) // → AppUser (sets session)
|
|
|
84
86
|
auth.logout() // clears session
|
|
85
87
|
```
|
|
86
88
|
|
|
87
|
-
For apps with an agent interface, the SDK also provides `createAgentChatClient()` for thread management and streaming chat.
|
|
89
|
+
For apps with an agent interface, the SDK also provides `createAgentChatClient()` for thread management and streaming chat. Load the `agentInterfaces` skill for its usage — thread APIs, streaming callbacks, and attachments are all there.
|
|
88
90
|
|
|
89
91
|
The project uses `"jsx": "react-jsx"` (automatic JSX transform) — do not `import React from 'react'`. Only import the specific hooks and types you need (e.g., `import { useState, useEffect } from 'react'`).
|
|
90
92
|
|
|
@@ -138,496 +140,49 @@ await prerender.invalidate(['/u/abc']); // omit arg to purge all
|
|
|
138
140
|
|
|
139
141
|
REST endpoints for external consumers — other services, mobile apps, integrations. This is separate from the web frontend's internal RPC (`@mindstudio-ai/interface` calls `/_/methods` directly and does not use the API interface). The API interface lives at `/_/api/` and exposes only the methods you choose to route.
|
|
140
142
|
|
|
141
|
-
Use it for
|
|
142
|
-
|
|
143
|
-
### Spec: `src/interfaces/api.md`
|
|
144
|
-
|
|
145
|
-
The human-readable spec. Frontmatter declares the API name and description; the body maps methods to REST routes using MSFM.
|
|
146
|
-
|
|
147
|
-
```yaml
|
|
148
|
-
---
|
|
149
|
-
name: Vendor Management API
|
|
150
|
-
description: API for managing vendors and purchase orders.
|
|
151
|
-
type: interface/api
|
|
152
|
-
---
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
Routes are declared as `VERB /path → methodExportName` under resource headings, with annotations for params and descriptions:
|
|
156
|
-
|
|
157
|
-
```markdown
|
|
158
|
-
## Vendors
|
|
159
|
-
|
|
160
|
-
### List vendors
|
|
161
|
-
GET /vendors → listVendors
|
|
162
|
-
~~~
|
|
163
|
-
Returns all vendors, optionally filtered by status.
|
|
164
|
-
query: status (string, optional) — filter by vendor status
|
|
165
|
-
~~~
|
|
166
|
-
|
|
167
|
-
### Create vendor
|
|
168
|
-
POST /vendors → submitVendorRequest
|
|
169
|
-
~~~
|
|
170
|
-
Submit a new vendor for approval.
|
|
171
|
-
body: name (string, required) — vendor name
|
|
172
|
-
contactEmail (string, required) — billing contact
|
|
173
|
-
~~~
|
|
174
|
-
|
|
175
|
-
### Delete vendor
|
|
176
|
-
DELETE /vendors/:vendorId → deleteVendor
|
|
177
|
-
~~~
|
|
178
|
-
path: vendorId (string, required) — the vendor's unique identifier
|
|
179
|
-
~~~
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### Compiled Output: `dist/interfaces/api/api.json`
|
|
183
|
-
|
|
184
|
-
```json
|
|
185
|
-
{
|
|
186
|
-
"api": {
|
|
187
|
-
"name": "Vendor Management API",
|
|
188
|
-
"description": "API for managing vendors and purchase orders.",
|
|
189
|
-
"routes": [
|
|
190
|
-
{
|
|
191
|
-
"method": "GET",
|
|
192
|
-
"path": "/vendors",
|
|
193
|
-
"handler": "list-vendors",
|
|
194
|
-
"summary": "List vendors",
|
|
195
|
-
"description": "Returns all vendors, optionally filtered by status.",
|
|
196
|
-
"tag": "Vendors",
|
|
197
|
-
"params": {
|
|
198
|
-
"query": {
|
|
199
|
-
"status": { "type": "string", "required": false, "description": "Filter by vendor status" }
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
"method": "POST",
|
|
205
|
-
"path": "/vendors",
|
|
206
|
-
"handler": "submit-vendor-request",
|
|
207
|
-
"summary": "Create vendor",
|
|
208
|
-
"description": "Submit a new vendor for approval.",
|
|
209
|
-
"tag": "Vendors",
|
|
210
|
-
"params": {
|
|
211
|
-
"body": {
|
|
212
|
-
"name": { "type": "string", "required": true, "description": "Vendor name" },
|
|
213
|
-
"contactEmail": { "type": "string", "required": true, "description": "Billing contact" }
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
"method": "DELETE",
|
|
219
|
-
"path": "/vendors/:vendorId",
|
|
220
|
-
"handler": "delete-vendor",
|
|
221
|
-
"summary": "Delete vendor",
|
|
222
|
-
"description": "Permanently remove a vendor.",
|
|
223
|
-
"tag": "Vendors",
|
|
224
|
-
"params": {
|
|
225
|
-
"path": {
|
|
226
|
-
"vendorId": { "type": "string", "required": true, "description": "The vendor's unique identifier" }
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
]
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
| Field | Description |
|
|
236
|
-
|-------|-------------|
|
|
237
|
-
| `name` | API display name (used in generated OpenAPI spec) |
|
|
238
|
-
| `description` | API description |
|
|
239
|
-
| `routes[].method` | HTTP method: `GET`, `POST`, `PUT`, `PATCH`, `DELETE` |
|
|
240
|
-
| `routes[].path` | URL path with `:param` placeholders for path params |
|
|
241
|
-
| `routes[].handler` | Method `id` from the manifest (kebab-case) |
|
|
242
|
-
| `routes[].summary` | Short description for the endpoint |
|
|
243
|
-
| `routes[].description` | Longer description |
|
|
244
|
-
| `routes[].tag` | Resource grouping (becomes a tag in OpenAPI) |
|
|
245
|
-
| `routes[].params` | Parameter declarations: `path`, `query`, and/or `body` objects |
|
|
143
|
+
Use it for sync endpoints for other services, a public REST API, batch tools — anything where something outside the app's own frontend needs to call a method over HTTP. It's also the interface that hands a method the raw HTTP request (`input._request`).
|
|
246
144
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
Routes are mounted at `/_/api{path}` (e.g. `DELETE /_/api/vendors/abc123`).
|
|
250
|
-
|
|
251
|
-
- **Path params** are extracted and merged into the method's input: `/:vendorId` → `{ vendorId: "abc123" }`
|
|
252
|
-
- **Query params** are merged into input for GET requests: `?status=approved` → `{ status: "approved" }`
|
|
253
|
-
- **Request body** for POST/PUT/PATCH is the input directly (no `{ input: {...} }` wrapper)
|
|
254
|
-
- **Response** is the method output directly (no `{ output: {...} }` wrapper)
|
|
255
|
-
- **Auth** via `Authorization: Bearer sk_...` (API key resolves to a user with full RBAC)
|
|
256
|
-
- **Streaming**: `Accept: text/event-stream` header returns SSE chunks
|
|
257
|
-
- **Raw request context**: Every API method receives `input._request` with `{ method, headers, rawBody }`. `rawBody` is the original unparsed body as a UTF-8 string — critical for webhook signature verification (Stripe, GitHub, Shopify). For most methods you don't need `_request` at all.
|
|
258
|
-
|
|
259
|
-
### Manifest
|
|
260
|
-
|
|
261
|
-
```json
|
|
262
|
-
{ "type": "api", "path": "dist/interfaces/api/api.json" }
|
|
263
|
-
```
|
|
145
|
+
**Load the `restApi` skill** before authoring `src/interfaces/api.md` or writing the config — the route spec format, param declarations, auth, and platform behaviour are all there.
|
|
264
146
|
|
|
265
147
|
## Platform-Triggered Interfaces
|
|
266
148
|
|
|
267
149
|
Cron, Webhook, and Email interfaces are invoked by the platform, not by a user session. Methods called through these interfaces run with `auth.roles: ['system']`. Use `auth.requireRole('system')` to restrict a method to platform triggers only.
|
|
268
150
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
Scheduled method execution.
|
|
151
|
+
Each has its own skill carrying the config shape, the input the method receives, and the platform's behaviour: `scheduledJobs`, `webhooks`, `inboundEmail`.
|
|
272
152
|
|
|
273
|
-
|
|
153
|
+
## Cron
|
|
274
154
|
|
|
275
|
-
|
|
276
|
-
{
|
|
277
|
-
"cron": {
|
|
278
|
-
"jobs": [
|
|
279
|
-
{
|
|
280
|
-
"schedule": "0 9 * * 5",
|
|
281
|
-
"method": "process-weekly-payments",
|
|
282
|
-
"description": "Process approved invoices every Friday at 9am"
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
"schedule": "*/30 * * * *",
|
|
286
|
-
"method": "sync-vendor-status",
|
|
287
|
-
"description": "Sync vendor statuses every 30 minutes"
|
|
288
|
-
}
|
|
289
|
-
]
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
```
|
|
155
|
+
Scheduled method execution — a method plus a cron expression, synced to the platform on deploy.
|
|
293
156
|
|
|
294
|
-
|
|
157
|
+
**Load the `scheduledJobs` skill** before adding one.
|
|
295
158
|
|
|
296
159
|
## Webhook
|
|
297
160
|
|
|
298
161
|
Inbound HTTP endpoints that invoke a method directly and synchronously — the caller waits for the method to finish. Use for receiving webhooks from external services (Stripe, GitHub, Shopify, Slack, Twilio). Direct inbound webhooks with signature verification work natively; do **not** build confirmation-token or polling workarounds.
|
|
299
162
|
|
|
300
|
-
|
|
163
|
+
Routing is by a secret in the URL rather than an auth header, which is what makes it the right fit for provider callbacks — they can't send a bearer token. The API interface is the alternative when the caller can.
|
|
301
164
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
```json
|
|
305
|
-
{
|
|
306
|
-
"webhook": {
|
|
307
|
-
"endpoints": [
|
|
308
|
-
{
|
|
309
|
-
"method": "handle-payment-webhook",
|
|
310
|
-
"secret": "whsec_pick_a_long_random_token",
|
|
311
|
-
"description": "Stripe events"
|
|
312
|
-
}
|
|
313
|
-
]
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
- `method` — the id of a method in `methods[]` to invoke.
|
|
319
|
-
- `secret` — a developer-chosen opaque token that is **both the routing key and the access guard**. It is stable across deploys (compilation is a passthrough — redeploying never rotates it), so a URL you register with Stripe/GitHub stays valid. Generate one long random value per endpoint and keep it constant.
|
|
320
|
-
- Declare multiple endpoints if needed; each `secret` maps to one method.
|
|
321
|
-
|
|
322
|
-
### Endpoint URL
|
|
323
|
-
|
|
324
|
-
Register this with the external service: `https://{app-host}/_/webhook/{secret}` — `{app-host}` is any host the app is served on: its `custom_subdomain` host (e.g. `myapp.madewithremy.com`), a custom domain if configured, or the UUID host (`<appId>.madewithremy.com` / `.msagent.ai`). All HTTP verbs are accepted.
|
|
325
|
-
|
|
326
|
-
### Input
|
|
327
|
-
|
|
328
|
-
The method receives:
|
|
329
|
-
|
|
330
|
-
```ts
|
|
331
|
-
{
|
|
332
|
-
method: string; // HTTP method
|
|
333
|
-
headers: Record<string, string>; // request headers
|
|
334
|
-
query: Record<string, string>; // query params
|
|
335
|
-
body: any; // parsed JSON / form body
|
|
336
|
-
rawBody: string; // exact raw request bytes (UTF-8), pre-parse
|
|
337
|
-
}
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
For signature verification **always use `rawBody`, never `body`** — providers (Stripe, GitHub, Shopify, Slack) HMAC the raw payload, and a re-serialized `body` will not match. E.g. `stripe.webhooks.constructEvent(input.rawBody, input.headers['stripe-signature'], endpointSecret)`. `rawBody` is populated for `application/json` and `application/x-www-form-urlencoded` bodies (what these providers send).
|
|
341
|
-
|
|
342
|
-
### Response
|
|
343
|
-
|
|
344
|
-
Whatever the method returns as output is sent back to the caller as JSON; if it returns no output, the platform responds `204`. A wrong/unknown secret returns `401`; an app with no live release returns `404`.
|
|
165
|
+
**Load the `webhooks` skill** before adding one — the secret semantics, endpoint URL, input shape, and signature verification are all there.
|
|
345
166
|
|
|
346
167
|
## Email
|
|
347
168
|
|
|
348
|
-
Inbound email triggers. Each app has one email-handler method; the platform routes all inbound mail destined for the app — across any of its address tiers — to that method.
|
|
349
|
-
|
|
350
|
-
### Address tiers
|
|
351
|
-
|
|
352
|
-
Three tiers, all delivered to the same handler method. The new tiers are catchall (no localpart registration); the legacy tier is specific-localpart and frozen for new apps.
|
|
353
|
-
|
|
354
|
-
| Tier | Address | How it's set up |
|
|
355
|
-
|---|---|---|
|
|
356
|
-
| Platform subdomain (default) | `*@<custom_subdomain>.madewithremy.com` | Automatic the moment the app has a `custom_subdomain` set. Every address on that subdomain delivers to the handler. |
|
|
357
|
-
| Custom domain | `*@<their-domain>` | The user adds a domain in the dashboard's email-domains settings and points one MX record at `mx.msagent.ai`. Not something the agent provisions. |
|
|
358
|
-
| Legacy `mindstudio-hooks.com` | `<name>@mindstudio-hooks.com` | Existing apps only — frozen for new apps. Don't recommend it; treat as read-only history. |
|
|
359
|
-
|
|
360
|
-
Because the new tiers are catchall, `to` carries an arbitrary localpart. Methods that need to branch on it should read `input.to` (e.g. `if (input.to.startsWith('support@')) ...`).
|
|
169
|
+
Inbound email triggers. Each app has one email-handler method; the platform routes all inbound mail destined for the app — across any of its address tiers — to that method. Addresses on the app's subdomain are catchall, so per-purpose addresses (`support@`, `receipts@`) work without registering anything.
|
|
361
170
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
### Config (`interface.json`)
|
|
365
|
-
|
|
366
|
-
```json
|
|
367
|
-
{
|
|
368
|
-
"email": {
|
|
369
|
-
"method": "handle-inbound-email",
|
|
370
|
-
"approvedSenders": ["billing@vendor.com", "*@trusted-partner.com"]
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
`approvedSenders` is optional. When set, only senders matching an exact address or `*@domain.com` wildcard reach the method; everything else is rejected by the platform with `400 invalid_sender` before the method runs (silently — the sender isn't bounced). Matching is case-insensitive. The same list applies uniformly across all three address tiers.
|
|
376
|
-
|
|
377
|
-
### Input shape
|
|
378
|
-
|
|
379
|
-
```ts
|
|
380
|
-
{
|
|
381
|
-
to: string; // full recipient address; localpart is arbitrary on catchall tiers
|
|
382
|
-
from: string; // bare sender address, extracted from "Name <a@b>" form
|
|
383
|
-
fromName: string | null; // sender display name, or null
|
|
384
|
-
subject: string; // 'No Subject' if missing
|
|
385
|
-
message: string; // plain-text body, falls back to HTML if text is missing; 'No Body' if neither was sent
|
|
386
|
-
html: string; // HTML body, or '' when text-only
|
|
387
|
-
attachments: string[]; // CDN URLs — already uploaded by the platform
|
|
388
|
-
messageId: string | null; // this email's Message-ID, angle-bracketed (<id@host>)
|
|
389
|
-
inReplyTo: string | null; // Message-ID this email is replying to, if any
|
|
390
|
-
references: string[]; // prior Message-IDs in the thread (angle-bracketed); [] if none
|
|
391
|
-
replyTo: string | null; // Reply-To address — reply here, not `from`, when set
|
|
392
|
-
cc: string[]; // Cc recipient addresses
|
|
393
|
-
date: string | null; // original send time, ISO-8601
|
|
394
|
-
}
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
To reply in-thread, feed these into `sendEmail`: set `inReplyTo` to the incoming `messageId` and `references` to `[...references, messageId]`. Send to `replyTo` when it's set, otherwise `from`. `sendEmail` returns `{ recipients, cc, bcc, from }` (who it sent to + the sender used); it does not return the sent message's own `Message-ID`, so thread off *inbound* mail, not off messages you sent.
|
|
398
|
-
|
|
399
|
-
### Attachments and size limits
|
|
400
|
-
|
|
401
|
-
`attachments[]` is an array of CDN URLs — the platform has already received and uploaded the files. Fetch them server-side via the URL when you need the bytes; pass them through as URLs to UI or downstream services.
|
|
402
|
-
|
|
403
|
-
Max inbound message size is 25 MB total (including all attachments). Oversized messages are rejected by the platform before the method runs.
|
|
404
|
-
|
|
405
|
-
### Auth
|
|
406
|
-
|
|
407
|
-
Methods invoked through this interface run with `auth.roles: ['system']` (see the system-roles section above). They have no user session and can't impersonate. Use `auth.requireRole('system')` to gate methods that should only be reachable via email.
|
|
171
|
+
**Load the `inboundEmail` skill** before writing the handler — address tiers, `approvedSenders`, the input shape, in-thread replies, and attachments are all there.
|
|
408
172
|
|
|
409
173
|
## MCP (Model Context Protocol)
|
|
410
174
|
|
|
411
175
|
Expose the app to *external* AI agents — Claude Desktop, Cursor, other people's agents, anything that speaks MCP. Unlike the agent interface (which *is* an agent — its own LLM, personality, and chat UI), MCP has no model of its own; it's the app projected as an MCP server for an outside AI to drive.
|
|
412
176
|
|
|
413
|
-
It supports the full MCP surface:
|
|
414
|
-
- **Tools** — methods the agent can call (rich descriptions + machine-readable annotations).
|
|
415
|
-
- **Resources** — read-only app data the agent can pull into context, addressable by URI.
|
|
416
|
-
- **Prompts** — reusable, parameterized prompt templates the server offers.
|
|
417
|
-
- **Instructions** — server-level guidance shown to the calling agent (the toolset's "system prompt").
|
|
418
|
-
|
|
419
|
-
The platform hosts the server, handles auth like the API interface (optional — keyed or anonymous), and derives every tool's input schema from the method contract. Because the consumer is an external agent with no knowledge of your app, **the descriptions are the product** — load the `mcpInterfaces` skill for how to write them.
|
|
420
|
-
|
|
421
|
-
### Spec: `src/interfaces/mcp.md`
|
|
422
|
-
|
|
423
|
-
Frontmatter declares the server. The body's intro prose becomes the server `instructions`; `## Tools`, `## Resources`, and `## Prompts` sections declare the rest.
|
|
424
|
-
|
|
425
|
-
```yaml
|
|
426
|
-
---
|
|
427
|
-
name: Vendor Management
|
|
428
|
-
description: Tools and data for managing vendors and purchase orders.
|
|
429
|
-
type: interface/mcp
|
|
430
|
-
---
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
```markdown
|
|
434
|
-
This server manages vendors and purchase orders. Read a vendor before updating it; submitted
|
|
435
|
-
requests go through approval before they become active.
|
|
436
|
-
|
|
437
|
-
## Tools
|
|
438
|
-
|
|
439
|
-
### Submit a vendor request
|
|
440
|
-
method: submit-vendor-request
|
|
441
|
-
~~~
|
|
442
|
-
Submit a new vendor for approval. Use when the caller wants to add a vendor.
|
|
443
|
-
Do NOT use to modify an existing vendor — that's update-vendor.
|
|
444
|
-
- name: the vendor's legal name
|
|
445
|
-
- contactEmail: billing contact; required for approval routing
|
|
446
|
-
Returns the created vendor's id and its initial "pending" status.
|
|
447
|
-
~~~
|
|
448
|
-
|
|
449
|
-
### List vendors
|
|
450
|
-
method: list-vendors
|
|
451
|
-
annotations: readOnly
|
|
452
|
-
~~~
|
|
453
|
-
List all vendors, newest first. Read-only.
|
|
454
|
-
~~~
|
|
455
|
-
|
|
456
|
-
## Resources
|
|
457
|
-
|
|
458
|
-
- list-vendors → app://vendors — "Vendors" — all vendors (application/json)
|
|
459
|
-
- get-vendor → app://vendors/{id} — "Vendor" — a single vendor by id (application/json)
|
|
460
|
-
|
|
461
|
-
## Prompts
|
|
462
|
-
|
|
463
|
-
### draft_vendor_email
|
|
464
|
-
description: Draft an outreach email to a vendor.
|
|
465
|
-
arguments: vendorId (required) — the vendor to contact
|
|
466
|
-
~~~
|
|
467
|
-
Write a warm outreach email to vendor {{vendorId}} introducing our procurement process.
|
|
468
|
-
~~~
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
Don't hand-author input schemas — the platform derives them. For a resource template, `{param}` in the URI maps to the backing method's input.
|
|
472
|
-
|
|
473
|
-
### Compiled Output: `dist/interfaces/mcp/`
|
|
474
|
-
|
|
475
|
-
```
|
|
476
|
-
dist/interfaces/mcp/
|
|
477
|
-
├── interface.json ← config the platform reads
|
|
478
|
-
├── instructions.md ← server-level guidance (returned in `initialize`)
|
|
479
|
-
├── tools/
|
|
480
|
-
│ ├── submitVendorRequest.md ← rich description, one per tool
|
|
481
|
-
│ └── listVendors.md
|
|
482
|
-
└── prompts/
|
|
483
|
-
└── draftVendorEmail.md ← prompt template body, one per prompt
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
Resources carry inline metadata only — no per-resource file.
|
|
487
|
-
|
|
488
|
-
### Config (`interface.json`)
|
|
489
|
-
|
|
490
|
-
```json
|
|
491
|
-
{
|
|
492
|
-
"mcp": {
|
|
493
|
-
"name": "Vendor Management",
|
|
494
|
-
"description": "Tools and data for managing vendors and purchase orders.",
|
|
495
|
-
"instructions": "instructions.md",
|
|
496
|
-
"tools": [
|
|
497
|
-
{
|
|
498
|
-
"method": "submit-vendor-request",
|
|
499
|
-
"name": "submit_vendor_request",
|
|
500
|
-
"title": "Submit Vendor Request",
|
|
501
|
-
"description": "tools/submitVendorRequest.md",
|
|
502
|
-
"annotations": { "readOnly": false, "destructive": false, "idempotent": false, "openWorld": false }
|
|
503
|
-
},
|
|
504
|
-
{
|
|
505
|
-
"method": "list-vendors",
|
|
506
|
-
"title": "List Vendors",
|
|
507
|
-
"description": "tools/listVendors.md",
|
|
508
|
-
"annotations": { "readOnly": true }
|
|
509
|
-
}
|
|
510
|
-
],
|
|
511
|
-
"resources": [
|
|
512
|
-
{ "method": "list-vendors", "uri": "app://vendors", "name": "Vendors", "description": "All vendors.", "mimeType": "application/json" },
|
|
513
|
-
{ "method": "get-vendor", "uriTemplate": "app://vendors/{id}", "name": "Vendor", "description": "A single vendor by id.", "mimeType": "application/json" }
|
|
514
|
-
],
|
|
515
|
-
"prompts": [
|
|
516
|
-
{
|
|
517
|
-
"name": "draft_vendor_email",
|
|
518
|
-
"title": "Draft vendor email",
|
|
519
|
-
"description": "Draft an outreach email to a vendor.",
|
|
520
|
-
"arguments": [ { "name": "vendorId", "description": "The vendor to contact", "required": true } ],
|
|
521
|
-
"template": "prompts/draftVendorEmail.md"
|
|
522
|
-
}
|
|
523
|
-
]
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
| Field | Description |
|
|
529
|
-
|-------|-------------|
|
|
530
|
-
| `name`, `description` | Server display name + registry metadata (not shown to the calling agent) |
|
|
531
|
-
| `instructions` | Relative path to the server-level guidance returned in `initialize` |
|
|
532
|
-
| `tools[].method` | Method `id` from the manifest (kebab-case) |
|
|
533
|
-
| `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 |
|
|
534
|
-
| `tools[].title` | Optional human-friendly display name |
|
|
535
|
-
| `tools[].description` | Relative path to the tool's markdown description |
|
|
536
|
-
| `tools[].annotations` | Optional client hints (auto-call vs. confirm): `readOnly`, `destructive`, `idempotent`, `openWorld` — map to MCP's `readOnlyHint` etc. |
|
|
537
|
-
| `resources[].method` | The read method invoked when the resource is read |
|
|
538
|
-
| `resources[].uri` / `uriTemplate` | A static URI, or a template whose `{param}` maps to the method's input |
|
|
539
|
-
| `resources[].name`, `description`, `mimeType` | Resource metadata |
|
|
540
|
-
| `prompts[].name`, `title`, `description` | Prompt identity + metadata |
|
|
541
|
-
| `prompts[].arguments` | `[{ name, description?, required? }]` |
|
|
542
|
-
| `prompts[].template` | Relative path to the template body (`{{arg}}` placeholders) |
|
|
543
|
-
|
|
544
|
-
There is no `inputSchema` field — the platform derives each tool's schema from the method's input contract.
|
|
545
|
-
|
|
546
|
-
### Platform Behavior
|
|
547
|
-
|
|
548
|
-
- The platform hosts the MCP server and exposes it to external clients. Clients connect at `POST https://{app-host}/_/mcp`.
|
|
549
|
-
- **Auth is optional**, identical to the API interface: a `Bearer` key resolves to a user with full RBAC; with no key, calls run anonymously (no user, no roles). The method is the boundary — gate sensitive tools with `auth.requireRole`/`requireUser`; a public (keyless) server exposes only the un-gated tools.
|
|
550
|
-
- Input schemas are derived automatically from each method's input contract.
|
|
551
|
-
- `tools/list` is static; access is enforced per-method at call time (a gated tool is listed but rejects an unauthorized call).
|
|
552
|
-
- A resource read invokes the backing method (template `{param}`s come from the URI) and returns its output as the resource contents.
|
|
553
|
-
- `prompts/get` fills the template with the provided arguments.
|
|
554
|
-
- `instructions` is returned in the `initialize` response.
|
|
555
|
-
|
|
556
|
-
### Manifest
|
|
177
|
+
It supports the full MCP surface: tools (methods the agent can call), resources (read-only app data addressable by URI), prompts (parameterized templates), and instructions (server-level guidance for the whole toolset). The platform hosts the server, handles auth, and derives every tool's input schema from the method contract.
|
|
557
178
|
|
|
558
|
-
|
|
559
|
-
{ "type": "mcp", "path": "dist/interfaces/mcp/interface.json" }
|
|
560
|
-
```
|
|
179
|
+
**Load the `mcpInterfaces` skill** before authoring `src/interfaces/mcp.md`. Because the consumer is an external agent with no knowledge of your app, the descriptions are the product — the skill carries both how to write them and the full config contract.
|
|
561
180
|
|
|
562
181
|
## Agent (Conversational Interface)
|
|
563
182
|
|
|
564
|
-
A conversational interface where an LLM has access to the app's methods as tools. Unlike MCP (which exposes methods for external agents), the agent interface IS the agent — it has its own personality, system prompt, and model config, and orchestrates tool calls against the app's methods internally.
|
|
565
|
-
|
|
566
|
-
This section is the wiring. Load the `agentInterfaces` skill before authoring the spec body or building the chat UI — what belongs in the agent's system prompt, how to write its tool descriptions, and the `createAgentChatClient()` frontend API are all there.
|
|
567
|
-
|
|
568
|
-
### Spec: `src/interfaces/agent.md`
|
|
569
|
-
|
|
570
|
-
The human-readable spec. Frontmatter contains structured fields; the prose body is the behavioral spec — voice, personality, capabilities, rules — written in MSFM.
|
|
571
|
-
|
|
572
|
-
```yaml
|
|
573
|
-
---
|
|
574
|
-
name: Todo Assistant
|
|
575
|
-
model: {"model": "claude-4-5-haiku", "temperature": 0.5, "maxResponseTokens": 16000}
|
|
576
|
-
description: Conversational agent that helps users manage their to-do list.
|
|
577
|
-
---
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
Frontmatter fields:
|
|
581
|
-
- `name` — agent display name
|
|
582
|
-
- `model` — JSON string with `model` (MindStudio model ID), `temperature`, `maxResponseTokens`, and optional `config` (model-specific settings like `reasoning`, `tools`, etc.). Use `askMindStudioSdk` to look up available model IDs and their config options when setting the model ID. The user's UI will have a nice visual picker to allow them to change it later, so only validate model when you're setting - otherwise assume this value to be correct if it changes.
|
|
583
|
-
- `description` — one-liner for agent card/listing
|
|
584
|
-
|
|
585
|
-
The prose body contains sections like Voice & Personality, Capabilities, Behavior — whatever structure serves the agent's character. This is compiled into the system prompt and tool descriptions.
|
|
586
|
-
|
|
587
|
-
### Compiled Output: `dist/interfaces/agent/`
|
|
183
|
+
A conversational interface where an LLM has access to the app's methods as tools. Unlike MCP (which exposes methods for external agents), the agent interface IS the agent — it has its own personality, system prompt, and model config, and orchestrates tool calls against the app's methods internally. Chat runs as the authenticated user, so every tool call carries that user's roles.
|
|
588
184
|
|
|
589
|
-
|
|
590
|
-
dist/interfaces/agent/
|
|
591
|
-
├── agent.json ← config the platform reads
|
|
592
|
-
├── system.md ← compiled system prompt
|
|
593
|
-
└── tools/
|
|
594
|
-
├── createTodo.md ← rich tool description per method
|
|
595
|
-
├── listTodos.md
|
|
596
|
-
└── ...
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
### Config (`agent.json`)
|
|
600
|
-
|
|
601
|
-
```json
|
|
602
|
-
{
|
|
603
|
-
"agent": {
|
|
604
|
-
"model": "claude-4-5-haiku",
|
|
605
|
-
"temperature": 0.5,
|
|
606
|
-
"maxTokens": 16000,
|
|
607
|
-
"systemPrompt": "system.md",
|
|
608
|
-
"tools": [
|
|
609
|
-
{ "method": "create-todo", "description": "tools/createTodo.md" },
|
|
610
|
-
{ "method": "list-todos", "description": "tools/listTodos.md" }
|
|
611
|
-
],
|
|
612
|
-
"webInterfacePath": "/chat"
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
| Field | Description |
|
|
618
|
-
|-------|-------------|
|
|
619
|
-
| `model` | MindStudio model ID (e.g. `claude-4-5-haiku`, `claude-5-sonnet`) |
|
|
620
|
-
| `temperature` | Model temperature |
|
|
621
|
-
| `maxTokens` | Max response tokens |
|
|
622
|
-
| `systemPrompt` | Relative path to the compiled system prompt markdown file |
|
|
623
|
-
| `tools` | Array of tool entries — `method` references a method `id` from the manifest, `description` is a relative path to a markdown file with rich tool docs (when to use, examples, edge cases, parameter guidance) |
|
|
624
|
-
| `webInterfacePath` | Optional. If the app has a web interface with a chat page, this path tells the IDE where to show the preview. Otherwise the agent is accessed via API. |
|
|
625
|
-
|
|
626
|
-
### Manifest Declaration
|
|
627
|
-
|
|
628
|
-
```json
|
|
629
|
-
{ "type": "agent", "path": "dist/interfaces/agent/agent.json" }
|
|
630
|
-
```
|
|
185
|
+
**Load the `agentInterfaces` skill** before authoring `src/interfaces/agent.md` or building the chat UI — the spec frontmatter, compiled output, `agent.json`, and the entire frontend surface are all there.
|
|
631
186
|
|
|
632
187
|
## Manifest Declaration
|
|
633
188
|
|
|
@@ -637,7 +192,7 @@ Each interface is declared in `mindstudio.json`:
|
|
|
637
192
|
{
|
|
638
193
|
"interfaces": [
|
|
639
194
|
{ "type": "web", "path": "dist/interfaces/web/web.json" },
|
|
640
|
-
{ "type": "api" },
|
|
195
|
+
{ "type": "api", "path": "dist/interfaces/api/api.json" },
|
|
641
196
|
{ "type": "cron", "path": "dist/interfaces/cron/interface.json" },
|
|
642
197
|
{ "type": "webhook", "path": "dist/interfaces/webhook/interface.json" },
|
|
643
198
|
{ "type": "email", "path": "dist/interfaces/email/interface.json" },
|
|
@@ -647,4 +202,4 @@ Each interface is declared in `mindstudio.json`:
|
|
|
647
202
|
}
|
|
648
203
|
```
|
|
649
204
|
|
|
650
|
-
|
|
205
|
+
An interface with nothing to configure can be declared with just its type; the rest point at a compiled config file. Set `"enabled": false` to skip an interface during build.
|
|
@@ -87,15 +87,9 @@ await mindstudio.sendEmail({
|
|
|
87
87
|
body: content, // markdown or HTML, auto-detected; bodyType overrides. cc/bcc/replyTo/attachments also supported
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
subject: `Re: ${input.subject}`,
|
|
94
|
-
body: reply,
|
|
95
|
-
inReplyTo: input.messageId ?? undefined,
|
|
96
|
-
references: input.messageId ? [...input.references, input.messageId] : input.references,
|
|
97
|
-
cc: input.cc, // reply-all
|
|
98
|
-
});
|
|
90
|
+
// Replying to inbound mail is the `inboundEmail` skill's job — load it before writing
|
|
91
|
+
// a handler. It has the full input shape and the threading rules; getting the headers
|
|
92
|
+
// wrong sends a reply that starts a new conversation instead of continuing one.
|
|
99
93
|
|
|
100
94
|
// Store a file → returns a stable URL (define the store at module scope; see Files & Storage)
|
|
101
95
|
const { url } = await Reports.put(buffer, { contentType: 'application/pdf', filename: 'report.pdf' });
|
|
@@ -323,7 +317,9 @@ input._request: {
|
|
|
323
317
|
}
|
|
324
318
|
```
|
|
325
319
|
|
|
326
|
-
`rawBody` preserves the exact bytes the client sent — whitespace, key ordering, encoding. Use it for
|
|
320
|
+
`rawBody` preserves the exact bytes the client sent — whitespace, key ordering, encoding. Use it for signature verification.
|
|
321
|
+
|
|
322
|
+
**There are two inbound HTTP interfaces and they expose the raw body differently.** This one is the API interface, at `input._request.rawBody`. The Webhook interface puts it at top-level `input.rawBody` and routes by a secret in the URL instead of a bearer token, which usually suits provider callbacks better since a provider can't send one. Load the `webhooks` skill before choosing — the example below is the API-interface shape and won't work unchanged in a webhook handler.
|
|
327
323
|
|
|
328
324
|
```typescript
|
|
329
325
|
export async function stripeWebhook(input: {
|