@mindstudio-ai/remy 0.1.257 → 0.1.259
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/automatedActions/approveInitialPlan.md +2 -2
- 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 +2 -2
- package/dist/prompt/static/team.md +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Agent Interfaces
|
|
3
|
-
what: Conversational AI as a first-class interface to the app — an LLM with authenticated, per-user access to the app's methods as tools, paired with a streaming chat UI. The platform handles auth, tool dispatch, threads, and streaming, so the work is authorship: who the agent is, which methods it can reach, and how each one is described to it. Any app whose methods do something interesting can be projected into a conversation this way, often as its most compelling surface. This reference covers
|
|
4
|
-
when: Before authoring `src/interfaces/agent.md`, compiling `dist/interfaces/agent/`, or building an agent's chat UI.
|
|
3
|
+
what: Conversational AI as a first-class interface to the app — an LLM with authenticated, per-user access to the app's methods as tools, paired with a streaming chat UI. The platform handles auth, tool dispatch, threads, and streaming, so the work is authorship: who the agent is, which methods it can reach, and how each one is described to it. Any app whose methods do something interesting can be projected into a conversation this way, often as its most compelling surface. This reference covers the whole feature — writing the agent spec, compiling it, and building the chat frontend.
|
|
4
|
+
when: Before authoring `src/interfaces/agent.md`, compiling `dist/interfaces/agent/`, or building an agent's chat UI.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Building Agent Interfaces
|
|
@@ -81,6 +81,8 @@ When building the `dist/interfaces/agent/`, consider the agent spec, as well as
|
|
|
81
81
|
|
|
82
82
|
**`agent.json`** — ties it together. Model config from frontmatter, paths to system prompt and tool files, optional `webInterfacePath`.
|
|
83
83
|
|
|
84
|
+
The exact shape of all three, and of the spec frontmatter they compile from, is in "The wiring" at the end of this document.
|
|
85
|
+
|
|
84
86
|
## Chat UI Design
|
|
85
87
|
|
|
86
88
|
When the agent has a web frontend (via `webInterfacePath`), the chat UI is a page within the web interface.
|
|
@@ -203,3 +205,90 @@ The chat UI uses the app's design system — colors, typography, voice from `@br
|
|
|
203
205
|
|
|
204
206
|
- Avoid designs that look like dated messaging apps from 2015
|
|
205
207
|
- Avoid robotic empty states ("Hello! I'm your AI assistant. How can I help you today?")
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
# The wiring
|
|
212
|
+
|
|
213
|
+
## Spec: `src/interfaces/agent.md`
|
|
214
|
+
|
|
215
|
+
The human-readable spec. Frontmatter contains structured fields; the prose body is the behavioral spec —
|
|
216
|
+
voice, personality, capabilities, rules — written in MSFM.
|
|
217
|
+
|
|
218
|
+
```yaml
|
|
219
|
+
---
|
|
220
|
+
name: Todo Assistant
|
|
221
|
+
model: {"model": "claude-4-5-haiku", "temperature": 0.5, "maxResponseTokens": 16000}
|
|
222
|
+
description: Conversational agent that helps users manage their to-do list.
|
|
223
|
+
---
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Frontmatter fields:
|
|
227
|
+
|
|
228
|
+
- `name` — agent display name
|
|
229
|
+
- `model` — JSON string with `model` (MindStudio model ID), `temperature`, `maxResponseTokens`, and
|
|
230
|
+
optional `config` (model-specific settings like `reasoning`, `tools`, etc.). Ask `askMindStudioSdk`
|
|
231
|
+
for available model IDs and their config options — MindStudio's ids don't match vendor ids, so treat
|
|
232
|
+
any id in this document's examples as illustrative rather than current. The user's UI has a visual picker for changing it later, so only validate
|
|
233
|
+
the model when you're setting it; if the value changes afterwards, assume it's correct.
|
|
234
|
+
- `description` — one-liner for agent card/listing
|
|
235
|
+
|
|
236
|
+
The prose body contains sections like Voice & Personality, Capabilities, Behavior — whatever structure
|
|
237
|
+
serves the agent's character. This is compiled into the system prompt and tool descriptions.
|
|
238
|
+
|
|
239
|
+
## Compiled Output: `dist/interfaces/agent/`
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
dist/interfaces/agent/
|
|
243
|
+
├── agent.json ← config the platform reads
|
|
244
|
+
├── system.md ← compiled system prompt
|
|
245
|
+
└── tools/
|
|
246
|
+
├── createTodo.md ← rich tool description per method
|
|
247
|
+
├── listTodos.md
|
|
248
|
+
└── ...
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Config (`agent.json`)
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
{
|
|
255
|
+
"agent": {
|
|
256
|
+
"model": "claude-4-5-haiku",
|
|
257
|
+
"temperature": 0.5,
|
|
258
|
+
"maxTokens": 16000,
|
|
259
|
+
"systemPrompt": "system.md",
|
|
260
|
+
"tools": [
|
|
261
|
+
{ "method": "create-todo", "description": "tools/createTodo.md" },
|
|
262
|
+
{ "method": "list-todos", "description": "tools/listTodos.md" }
|
|
263
|
+
],
|
|
264
|
+
"webInterfacePath": "/chat"
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**The token-limit field is renamed during compilation.** The spec frontmatter calls it
|
|
270
|
+
`maxResponseTokens`; the compiled `agent.json` calls it `maxTokens`. Same value, two names — carry it
|
|
271
|
+
across rather than copying the key.
|
|
272
|
+
|
|
273
|
+
| Field | Description |
|
|
274
|
+
|-------|-------------|
|
|
275
|
+
| `model` | MindStudio model ID. Comes from the spec frontmatter; look it up with `askMindStudioSdk` rather than guessing |
|
|
276
|
+
| `temperature` | Model temperature |
|
|
277
|
+
| `maxTokens` | Max response tokens (the spec's `maxResponseTokens`) |
|
|
278
|
+
| `systemPrompt` | Relative path to the compiled system prompt markdown file |
|
|
279
|
+
| `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) |
|
|
280
|
+
| `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. |
|
|
281
|
+
|
|
282
|
+
Declare it in `mindstudio.json`:
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
{ "type": "agent", "path": "dist/interfaces/agent/agent.json" }
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Auth
|
|
289
|
+
|
|
290
|
+
Agent chat runs as the **authenticated user**, not as a system role — tool calls carry that user's
|
|
291
|
+
roles, so a method gated with `auth.requireRole` behaves exactly as it would if the user had called it
|
|
292
|
+
from the web frontend. That's what makes exposing real methods safe; it's also why role restrictions
|
|
293
|
+
belong in the tool descriptions, so the agent can decline gracefully instead of surfacing a rejection.
|
|
294
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Inbound Email
|
|
3
|
+
what: The app has its own email address, and mail sent to it runs a method. Every address on the app's subdomain routes to one handler, so `support@`, `receipts@` and `anything@` all arrive without registering anything — branch on the recipient in code. Attachments arrive as CDN URLs the platform has already uploaded, threading headers come through intact so replies land in the same conversation, and a verified custom domain both receives and sends under the app's own brand. "Forward a receipt and the app files it" is a real feature that costs one method.
|
|
4
|
+
when: Before writing an email-handler method, adding an `email` interface, or promising anything about what happens when a user emails the app.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Inbound Email Interfaces
|
|
8
|
+
|
|
9
|
+
Inbound email triggers. Each app has **one** email-handler method; the platform routes all inbound
|
|
10
|
+
mail destined for the app — across any of its address tiers — to that method.
|
|
11
|
+
|
|
12
|
+
The addresses themselves are configured at the project level by the user through the Remy platform.
|
|
13
|
+
Your job is the `interface.json` and the method that handles the mail, not domain registration or MX
|
|
14
|
+
records.
|
|
15
|
+
|
|
16
|
+
## Address tiers
|
|
17
|
+
|
|
18
|
+
Three tiers, all delivered to the same handler method. The new tiers are catchall (no localpart
|
|
19
|
+
registration); the legacy tier is specific-localpart and frozen for new apps.
|
|
20
|
+
|
|
21
|
+
| Tier | Address | How it's set up |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| 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. |
|
|
24
|
+
| 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. |
|
|
25
|
+
| Legacy `mindstudio-hooks.com` | `<name>@mindstudio-hooks.com` | Existing apps only — frozen for new apps. Don't recommend it; treat as read-only history. |
|
|
26
|
+
|
|
27
|
+
Because the new tiers are catchall, `to` carries an arbitrary localpart. Methods that need to branch on
|
|
28
|
+
it should read `input.to` (e.g. `if (input.to.startsWith('support@')) ...`). This is what makes
|
|
29
|
+
per-purpose addresses free: you don't register `support@` anywhere, you just check for it.
|
|
30
|
+
|
|
31
|
+
A verified custom domain (and the app's `madewithremy.com` subdomain) also **sends** outbound mail, not
|
|
32
|
+
just receives — `sendEmail` picks the app's own-brand sender automatically, configured in the
|
|
33
|
+
dashboard's **Email** settings.
|
|
34
|
+
|
|
35
|
+
## Config (`interface.json`)
|
|
36
|
+
|
|
37
|
+
The top-level key must match the interface type (`email`):
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"email": {
|
|
42
|
+
"method": "handle-inbound-email",
|
|
43
|
+
"approvedSenders": ["billing@vendor.com", "*@trusted-partner.com"]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`approvedSenders` is optional. When set, only senders matching an exact address or `*@domain.com`
|
|
49
|
+
wildcard reach the method; everything else is rejected by the platform with `400 invalid_sender` before
|
|
50
|
+
the method runs (silently — the sender isn't bounced). Matching is case-insensitive. The same list
|
|
51
|
+
applies uniformly across all three address tiers.
|
|
52
|
+
|
|
53
|
+
Declare it in `mindstudio.json`:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{ "type": "email", "path": "dist/interfaces/email/interface.json" }
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Input shape
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
{
|
|
63
|
+
to: string; // full recipient address; localpart is arbitrary on catchall tiers
|
|
64
|
+
from: string; // bare sender address, extracted from "Name <a@b>" form
|
|
65
|
+
fromName: string | null; // sender display name, or null
|
|
66
|
+
subject: string; // 'No Subject' if missing
|
|
67
|
+
message: string; // plain-text body, falls back to HTML if text is missing; 'No Body' if neither was sent
|
|
68
|
+
html: string; // HTML body, or '' when text-only
|
|
69
|
+
attachments: string[]; // CDN URLs — already uploaded by the platform
|
|
70
|
+
messageId: string | null; // this email's Message-ID, angle-bracketed (<id@host>)
|
|
71
|
+
inReplyTo: string | null; // Message-ID this email is replying to, if any
|
|
72
|
+
references: string[]; // prior Message-IDs in the thread (angle-bracketed); [] if none
|
|
73
|
+
replyTo: string | null; // Reply-To address — reply here, not `from`, when set
|
|
74
|
+
cc: string[]; // Cc recipient addresses
|
|
75
|
+
date: string | null; // original send time, ISO-8601
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Replying in thread
|
|
80
|
+
|
|
81
|
+
Replies go out through the SDK's `sendEmail` action (its full parameter list is in the SDK actions
|
|
82
|
+
reference in your system prompt). Set `inReplyTo` to the incoming `messageId` and `references` to
|
|
83
|
+
`[...references, messageId]`. Send to `replyTo` when it's set, otherwise `from`.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
await mindstudio.sendEmail({
|
|
87
|
+
to: input.replyTo ?? input.from,
|
|
88
|
+
subject: `Re: ${input.subject}`,
|
|
89
|
+
body: reply,
|
|
90
|
+
inReplyTo: input.messageId ?? undefined,
|
|
91
|
+
references: input.messageId
|
|
92
|
+
? [...input.references, input.messageId]
|
|
93
|
+
: input.references,
|
|
94
|
+
cc: input.cc, // reply-all
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`sendEmail` returns `{ recipients, cc, bcc, from }` — who it sent to and the sender used. It does
|
|
99
|
+
**not** return the sent message's own `Message-ID`, so thread off *inbound* mail, never off messages
|
|
100
|
+
you sent.
|
|
101
|
+
|
|
102
|
+
## Attachments and size limits
|
|
103
|
+
|
|
104
|
+
`attachments[]` is an array of CDN URLs — the platform has already received and uploaded the files.
|
|
105
|
+
Fetch them server-side via the URL when you need the bytes; pass them through as URLs to UI or
|
|
106
|
+
downstream services.
|
|
107
|
+
|
|
108
|
+
Max inbound message size is 25 MB total (including all attachments). Oversized messages are rejected by
|
|
109
|
+
the platform before the method runs.
|
|
110
|
+
|
|
111
|
+
## Auth
|
|
112
|
+
|
|
113
|
+
Methods invoked through this interface run with `auth.roles: ['system']` — the platform is calling, not
|
|
114
|
+
a user session, so there's no user to impersonate. Use `auth.requireRole('system')` to gate methods that
|
|
115
|
+
should only be reachable via email. The auth reference in your system prompt covers the system role in
|
|
116
|
+
full.
|
|
@@ -1,16 +1,35 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: MCP Interfaces
|
|
3
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
|
|
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
5
|
---
|
|
6
6
|
|
|
7
|
-
#
|
|
7
|
+
# MCP Interfaces
|
|
8
8
|
|
|
9
|
-
|
|
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.
|
|
10
24
|
|
|
11
25
|
## The descriptions are the product
|
|
12
26
|
|
|
13
|
-
The calling agent is a stranger with no knowledge of your app. It decides what to invoke entirely from
|
|
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.
|
|
14
33
|
|
|
15
34
|
What that looks like in practice — the same method, described twice:
|
|
16
35
|
|
|
@@ -28,46 +47,69 @@ full updated vendor. Editors and admins only; other roles are rejected. For a
|
|
|
28
47
|
vendor that doesn't exist yet, use `createVendor`.
|
|
29
48
|
```
|
|
30
49
|
|
|
31
|
-
The weak one is what a schema already tells the caller. The strong one carries the three things a schema
|
|
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.
|
|
32
53
|
|
|
33
54
|
## Curate — not every method is a tool
|
|
34
55
|
|
|
35
|
-
Expose what an outside agent would actually use. Skip internal helpers, admin-only methods, and batch
|
|
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.
|
|
36
60
|
|
|
37
61
|
## Annotations
|
|
38
62
|
|
|
39
|
-
Annotations are machine-readable hints clients use to decide whether to auto-call a tool or ask the user
|
|
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:
|
|
40
65
|
|
|
41
|
-
- `readOnly` — the tool only reads, never mutates. The highest-value hint: clients auto-call reads
|
|
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.
|
|
42
68
|
- `destructive` — the tool can delete or overwrite. Clients gate these behind confirmation.
|
|
43
69
|
- `idempotent` — calling twice with the same arguments has the same effect as calling once.
|
|
44
|
-
- `openWorld` — the tool reaches outside the app (external web/services) rather than operating only on
|
|
70
|
+
- `openWorld` — the tool reaches outside the app (external web/services) rather than operating only on
|
|
71
|
+
app data.
|
|
45
72
|
|
|
46
|
-
The judgement is per tool, and getting `readOnly` right is what makes a toolset feel responsive rather
|
|
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):
|
|
47
76
|
|
|
48
77
|
```jsonc
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
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
|
+
]
|
|
54
85
|
```
|
|
55
86
|
|
|
56
|
-
Set them honestly rather than defensively. Marking a read `destructive` to be safe means the caller's
|
|
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.
|
|
57
89
|
|
|
58
90
|
## Tools vs. resources
|
|
59
91
|
|
|
60
|
-
A **tool** is an action the agent *invokes*; a **resource** is data the agent *reads into context*. A
|
|
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.
|
|
61
95
|
|
|
62
|
-
Resources are method-backed: a read invokes the method. Use a static `uri` for a fixed collection
|
|
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.
|
|
63
99
|
|
|
64
100
|
## Prompts
|
|
65
101
|
|
|
66
|
-
Prompts are reusable, parameterized templates the server offers to clients — e.g. a "draft a vendor
|
|
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.
|
|
67
105
|
|
|
68
106
|
## Server instructions
|
|
69
107
|
|
|
70
|
-
The spec's intro prose becomes the server `instructions` — toolset-level guidance returned to the
|
|
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.
|
|
71
113
|
|
|
72
114
|
```markdown
|
|
73
115
|
This server exposes a procurement app. Vendors are the central record and
|
|
@@ -77,4 +119,162 @@ search tool before calling anything that takes one. Search results are capped
|
|
|
77
119
|
at 50; page with the returned cursor rather than broadening the query.
|
|
78
120
|
```
|
|
79
121
|
|
|
80
|
-
That's four sentences doing what no individual tool description could: it explains the shape of the
|
|
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.
|