@mastra/mcp-docs-server 1.2.19-alpha.0 → 1.2.19-alpha.14
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/.docs/docs/channels.md +1 -0
- package/.docs/docs/deployment/mastra-server.md +19 -0
- package/.docs/docs/deployment/workers.md +2 -2
- package/.docs/docs/evals/built-in-scorers.md +1 -0
- package/.docs/docs/evals/multi-turn.md +84 -1
- package/.docs/docs/evals/overview.md +63 -1
- package/.docs/docs/harness/durable-agents.md +1 -1
- package/.docs/docs/mastra-platform/deploy.md +101 -0
- package/.docs/docs/mastra-platform/server.md +6 -11
- package/.docs/docs/mastra-platform/studio.md +8 -10
- package/.docs/docs/observability/integrations/exporters/mastra-storage.md +19 -14
- package/.docs/docs/sandbox/filesystem.md +8 -8
- package/.docs/docs/sandbox/overview.md +33 -66
- package/.docs/docs/server/middleware.md +4 -0
- package/.docs/docs/server/server-adapters.md +12 -8
- package/.docs/docs/storage.md +2 -0
- package/.docs/integrations/channels/imessage.md +150 -8
- package/.docs/integrations/databases/elasticsearch.md +156 -0
- package/.docs/integrations/databases/libsql.md +16 -0
- package/.docs/integrations/databases/mongodb.md +1 -1
- package/.docs/integrations/databases/postgresql.md +26 -0
- package/.docs/integrations/databases/valkey.md +99 -0
- package/.docs/integrations/deploy/render.md +47 -61
- package/.docs/integrations/sandboxes/e2b.md +2 -0
- package/.docs/integrations/tools/parallel.md +240 -0
- package/.docs/integrations.md +3 -0
- package/.docs/models/environment-variables.md +2 -0
- package/.docs/models/gateways/merge-gateway.md +2 -1
- package/.docs/models/gateways/netlify.md +10 -5
- package/.docs/models/gateways/openrouter.md +8 -9
- package/.docs/models/gateways/vercel.md +7 -6
- package/.docs/models/index.md +1 -1
- package/.docs/models/providers/agentrouter.md +17 -34
- package/.docs/models/providers/aki-io.md +14 -13
- package/.docs/models/providers/chutes.md +2 -2
- package/.docs/models/providers/crof.md +3 -8
- package/.docs/models/providers/crossmodel.md +56 -55
- package/.docs/models/providers/deepseek.md +8 -7
- package/.docs/models/providers/digitalocean.md +11 -11
- package/.docs/models/providers/edenai.md +14 -14
- package/.docs/models/providers/google.md +3 -3
- package/.docs/models/providers/hyper.md +7 -7
- package/.docs/models/providers/inceptron.md +1 -1
- package/.docs/models/providers/kilo.md +24 -20
- package/.docs/models/providers/llmgateway-providers.md +17 -8
- package/.docs/models/providers/llmgateway.md +3 -5
- package/.docs/models/providers/nano-gpt.md +24 -13
- package/.docs/models/providers/nvidia.md +3 -1
- package/.docs/models/providers/ofox.md +114 -110
- package/.docs/models/providers/opencode-go.md +26 -23
- package/.docs/models/providers/opencode.md +1 -1
- package/.docs/models/providers/opper.md +112 -0
- package/.docs/models/providers/requesty.md +1 -1
- package/.docs/models/providers/scaleway.md +2 -1
- package/.docs/models/providers.md +2 -0
- package/.docs/reference/agents/channels.md +1 -1
- package/.docs/reference/ai-sdk/handle-chat-stream.md +11 -0
- package/.docs/reference/ai-sdk/with-sse-heartbeat.md +47 -0
- package/.docs/reference/core/mastra-class.md +1 -1
- package/.docs/reference/evals/checks.md +6 -0
- package/.docs/reference/evals/multi-turn-judge.md +101 -0
- package/.docs/reference/index.md +4 -0
- package/.docs/reference/pubsub/valkey-streams.md +84 -0
- package/.docs/reference/rag/vector-databases.md +4 -4
- package/.docs/reference/server/express-adapter.md +6 -8
- package/.docs/reference/server/hono-adapter.md +19 -6
- package/.docs/reference/storage/turso.md +88 -0
- package/.docs/reference/streaming/ChunkType.md +29 -1
- package/.docs/reference/streaming/agents/stream.md +1 -3
- package/.docs/reference/tools/mcp-client.md +41 -9
- package/.docs/reference/vectors/mongodb.md +11 -11
- package/.docs/reference/vectors/pg.md +2 -0
- package/.docs/reference/workspace/sandbox.md +29 -1
- package/.docs/reference/workspace/workspace-class.md +15 -3
- package/CHANGELOG.md +66 -0
- package/package.json +5 -5
|
@@ -7,7 +7,7 @@ A filesystem gives an agent tools for reading, writing, listing, and [searching]
|
|
|
7
7
|
Configure files in two ways:
|
|
8
8
|
|
|
9
9
|
- [Direct filesystem access](#direct-filesystem-access) uses `filesystem` with one filesystem provider or a [`CompositeFilesystem`](#manual-composition) that you create yourself.
|
|
10
|
-
- [Mounts](#mounts) uses `mounts` to create a `CompositeFilesystem` from path-prefixed providers. When
|
|
10
|
+
- [Mounts](#mounts) uses `mounts` to create a `CompositeFilesystem` from path-prefixed providers. When a static sandbox and filesystem provider support mounting, Mastra automatically mounts the provider at its configured path. Remote sandbox mounts typically use Filesystem in Userspace (FUSE).
|
|
11
11
|
|
|
12
12
|
Configure either `filesystem` or `mounts`, not both. Configuring both throws a `WorkspaceError` with the code `INVALID_CONFIG`.
|
|
13
13
|
|
|
@@ -71,7 +71,7 @@ See [Search](https://mastra.ai/docs/sandbox/search) to index the files for keywo
|
|
|
71
71
|
|
|
72
72
|
## Mounts
|
|
73
73
|
|
|
74
|
-
Use `mounts` when programs inside a sandbox need to access persistent files by path. The agent still receives file tools, while command tools can run commands such as `ls`, `cat`, or `python` against the same files.
|
|
74
|
+
Use `mounts` when programs inside a sandbox need to access persistent files by path. Mastra creates the mount automatically when the sandbox and filesystem provider support it. The agent still receives file tools, while command tools can run commands such as `ls`, `cat`, or `python` against the same files.
|
|
75
75
|
|
|
76
76
|
For example, mount an S3 bucket at `/workspace` inside a Daytona sandbox:
|
|
77
77
|
|
|
@@ -180,7 +180,7 @@ Use manual composition when another part of your application needs the composite
|
|
|
180
180
|
|
|
181
181
|
## Mount availability
|
|
182
182
|
|
|
183
|
-
File tools and composite routing work without
|
|
183
|
+
File tools and composite routing work without a sandbox mount. When a remote sandbox and filesystem provider support mounting, `mounts` automatically uses FUSE to make the files visible to commands. `LocalSandbox` uses symlinks instead.
|
|
184
184
|
|
|
185
185
|
Built-in sandbox mounting currently includes:
|
|
186
186
|
|
|
@@ -195,27 +195,27 @@ Remote mounts may require `s3fs`, `gcsfuse`, or `blobfuse2` inside the sandbox.
|
|
|
195
195
|
|
|
196
196
|
If a sandbox mount is unavailable or fails, the workspace remains usable. File tools continue to access the provider through its SDK, but commands can't see that path. Mastra describes these providers to the agent as available through file tools only.
|
|
197
197
|
|
|
198
|
-
##
|
|
198
|
+
## Filesystems per user or thread
|
|
199
199
|
|
|
200
200
|
The `filesystem` option accepts a resolver when storage should vary by request, user, role, or tenant:
|
|
201
201
|
|
|
202
202
|
```typescript
|
|
203
203
|
const workspace = new Workspace({
|
|
204
204
|
filesystem: ({ requestContext }) => {
|
|
205
|
-
const
|
|
205
|
+
const userId = requestContext.get('user-id') as string
|
|
206
206
|
|
|
207
207
|
return new S3Filesystem({
|
|
208
208
|
bucket: process.env.S3_BUCKET!,
|
|
209
209
|
region: process.env.S3_REGION!,
|
|
210
|
-
prefix: `
|
|
210
|
+
prefix: `users/${userId}`,
|
|
211
211
|
})
|
|
212
212
|
},
|
|
213
213
|
})
|
|
214
214
|
```
|
|
215
215
|
|
|
216
|
-
Each
|
|
216
|
+
Each user gets a separate filesystem view, and file tools resolve the provider from the request context automatically.
|
|
217
217
|
|
|
218
|
-
`mounts` doesn't accept a resolver and can't be combined with a sandbox resolver. When each user or thread needs separate storage inside a separate sandbox, create and mount the provider inside the sandbox resolver. See [
|
|
218
|
+
`mounts` doesn't accept a resolver and can't be combined with a sandbox resolver. When each user or thread needs separate storage inside a separate sandbox, create and mount the provider inside the sandbox resolver. See [Sandboxes per user or thread](https://mastra.ai/docs/sandbox/overview).
|
|
219
219
|
|
|
220
220
|
## Policies and containment
|
|
221
221
|
|
|
@@ -2,27 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
# Sandboxes and filesystems
|
|
4
4
|
|
|
5
|
-
A sandbox gives your agent an environment where it can run commands, execute code, install dependencies, and manage processes.
|
|
5
|
+
A sandbox gives your agent an isolated environment where it can run commands, execute code, install dependencies, and manage processes. This lets agents perform work that would be risky, resource-intensive, or impractical to run directly inside your application.
|
|
6
6
|
|
|
7
|
-
[
|
|
7
|
+
Sandboxes are often temporary, so files created inside them may disappear when the environment stops. A [filesystem](https://mastra.ai/docs/sandbox/filesystem) gives the agent a place to read, write, and [search](https://mastra.ai/docs/sandbox/search) files that can outlive the sandbox. You can use one to keep outputs between runs, seed a new sandbox with existing files, or give the agent documents it can search while working. Filesystems also work without a sandbox, for example when an agent only needs a knowledge base or access to files in a service such as Google Drive.
|
|
8
8
|
|
|
9
9
|
## When to use sandboxes
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Use a sandbox when an agent needs to:
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
13
|
+
- Clone repositories and run shell, Git, build, or test workflows in a separate environment.
|
|
14
|
+
- Process PDFs, presentations, or other files with specialized libraries and produce new artifacts.
|
|
15
|
+
- Run long-lived or parallel work in separate environments with isolated files and processes.
|
|
16
16
|
|
|
17
17
|
If an agent only needs to read, write, or search files, configure [direct filesystem access](https://mastra.ai/docs/sandbox/filesystem).
|
|
18
18
|
|
|
19
19
|
## Quickstart
|
|
20
20
|
|
|
21
|
-
Give an agent a local sandbox
|
|
21
|
+
Give an agent a local sandbox:
|
|
22
22
|
|
|
23
23
|
```typescript
|
|
24
24
|
import { Agent } from '@mastra/core/agent'
|
|
25
|
-
import {
|
|
25
|
+
import { LocalSandbox, Workspace } from '@mastra/core/workspace'
|
|
26
26
|
|
|
27
27
|
export const codingAgent = new Agent({
|
|
28
28
|
id: 'coding-agent',
|
|
@@ -33,20 +33,15 @@ export const codingAgent = new Agent({
|
|
|
33
33
|
sandbox: new LocalSandbox({
|
|
34
34
|
workingDirectory: './workspace',
|
|
35
35
|
}),
|
|
36
|
-
filesystem: new LocalFilesystem({
|
|
37
|
-
basePath: './workspace',
|
|
38
|
-
}),
|
|
39
36
|
}),
|
|
40
37
|
})
|
|
41
38
|
|
|
42
39
|
await codingAgent.generate('List the files in the sandbox directory')
|
|
43
40
|
```
|
|
44
41
|
|
|
45
|
-
> **Warning:** `LocalSandbox`
|
|
46
|
-
|
|
47
|
-
To use a different sandbox for each user, tenant, or thread, configure a [sandbox resolver](#multi-tenant-sandboxes) instead.
|
|
42
|
+
> **Warning:** `LocalSandbox` runs commands on the application host by default and isn't isolated or secure. Enable [native isolation](#localsandbox), or use a remote or container sandbox when running untrusted code.
|
|
48
43
|
|
|
49
|
-
|
|
44
|
+
A static sandbox is shared across every request and memory thread that uses the agent. Use a [resolver](#sandboxes-per-user-or-thread) when each user or thread needs a separate environment. See [Lifecycle and persistence](#lifecycle-and-persistence) for sharing and cleanup details.
|
|
50
45
|
|
|
51
46
|
## Using the sandbox
|
|
52
47
|
|
|
@@ -74,7 +69,7 @@ const workspace = new Workspace({
|
|
|
74
69
|
})
|
|
75
70
|
```
|
|
76
71
|
|
|
77
|
-
|
|
72
|
+
Set `{ enabled: false }` on one tool to remove it, or set the top-level `enabled: false` to disable generated tools by default. A per-tool `{ enabled: true }` overrides that global setting. The top-level `requireApproval` policy applies to every generated tool unless a per-tool entry overrides it.
|
|
78
73
|
|
|
79
74
|
See the [sandbox tools reference](https://mastra.ai/reference/workspace/workspace-class) for all generated tools and the [tool configuration reference](https://mastra.ai/reference/workspace/workspace-class) for approvals, output limits, and hooks.
|
|
80
75
|
|
|
@@ -109,17 +104,12 @@ export const startServerTool = createTool({
|
|
|
109
104
|
})
|
|
110
105
|
```
|
|
111
106
|
|
|
112
|
-
##
|
|
107
|
+
## Sandboxes
|
|
113
108
|
|
|
114
109
|
### `LocalSandbox`
|
|
115
110
|
|
|
116
111
|
[`LocalSandbox`](https://mastra.ai/reference/workspace/local-sandbox) executes commands on the same machine as your Mastra application. By default, commands run directly on the host with the permissions of the application process.
|
|
117
112
|
|
|
118
|
-
Enable native isolation to restrict filesystem and network access at the operating-system level:
|
|
119
|
-
|
|
120
|
-
- **macOS**: Seatbelt (`sandbox-exec`)
|
|
121
|
-
- **Linux**: Bubblewrap (`bwrap`)
|
|
122
|
-
|
|
123
113
|
```typescript
|
|
124
114
|
const sandbox = new LocalSandbox({
|
|
125
115
|
workingDirectory: './workspace',
|
|
@@ -131,11 +121,13 @@ const sandbox = new LocalSandbox({
|
|
|
131
121
|
})
|
|
132
122
|
```
|
|
133
123
|
|
|
134
|
-
|
|
124
|
+
Enable native isolation to restrict filesystem and network access at the operating-system level. On macOS, native isolation uses Seatbelt (`sandbox-exec`). On Linux, it uses Bubblewrap (`bwrap`). Use `LocalSandbox.detectIsolation()` to check whether Seatbelt or Bubblewrap is available on the current operating system.
|
|
135
125
|
|
|
136
|
-
|
|
126
|
+
The [`nativeSandbox` options](https://mastra.ai/reference/workspace/local-sandbox) control network access, read-only or writable paths, write access to the working directory, and system binaries. You can also provide a custom Seatbelt profile or Bubblewrap arguments. See [`LocalSandbox`](https://mastra.ai/reference/workspace/local-sandbox) for the full configuration.
|
|
137
127
|
|
|
138
|
-
|
|
128
|
+
### Remote sandboxes
|
|
129
|
+
|
|
130
|
+
Use a remote or container sandbox when commands need a stronger boundary from the host application or when workloads need to scale beyond the resources of your application server. Each sandbox has its own isolation, persistence, networking, and mount behavior:
|
|
139
131
|
|
|
140
132
|
- [AgentCore](https://mastra.ai/integrations/sandboxes/agentcore)
|
|
141
133
|
- [Apple Container](https://mastra.ai/integrations/sandboxes/apple-container)
|
|
@@ -149,47 +141,40 @@ Use a remote or container backend when commands need a stronger boundary from th
|
|
|
149
141
|
- [Railway](https://mastra.ai/integrations/sandboxes/railway)
|
|
150
142
|
- [Vercel](https://mastra.ai/integrations/sandboxes/vercel)
|
|
151
143
|
|
|
152
|
-
If Mastra doesn't support your
|
|
144
|
+
If Mastra doesn't support your sandbox provider, implement the [sandbox provider interface](https://mastra.ai/reference/workspace/sandbox) to add it.
|
|
153
145
|
|
|
154
146
|
## Filesystem
|
|
155
147
|
|
|
156
|
-
|
|
148
|
+
Every sandbox has a filesystem for commands. `LocalSandbox` uses the host filesystem, while remote sandboxes have isolated filesystems that are often temporary. For files that need to outlive a sandbox, use external storage.
|
|
149
|
+
|
|
150
|
+
Mastra filesystems connect agents and sandboxes to provider-backed storage such as Amazon S3, Google Cloud Storage, or Google Drive. Configuring one gives the agent tools to read, write, and search files. When a remote sandbox and provider support mounting, Mastra automatically mounts the storage through Filesystem in Userspace (FUSE). Commands use normal file paths while the provider stores changes outside the sandbox.
|
|
157
151
|
|
|
158
|
-
|
|
152
|
+
See [Filesystem](https://mastra.ai/docs/sandbox/filesystem) for providers, file tools, and mounts.
|
|
159
153
|
|
|
160
|
-
##
|
|
154
|
+
## Sandboxes per user or thread
|
|
161
155
|
|
|
162
156
|
Use a **resolver** when each user, tenant, or thread needs a separate sandbox. Set `sandboxCacheKey` to the identity that owns the sandbox so later requests reuse the same live environment.
|
|
163
157
|
|
|
164
158
|
This example creates and caches one Daytona sandbox per user:
|
|
165
159
|
|
|
166
160
|
```typescript
|
|
167
|
-
import type { RequestContext } from '@mastra/core/request-context'
|
|
168
161
|
import { Workspace } from '@mastra/core/workspace'
|
|
169
162
|
import { DaytonaSandbox } from '@mastra/daytona'
|
|
170
163
|
|
|
171
|
-
const getUserId = (requestContext: RequestContext) => {
|
|
172
|
-
const userId = requestContext.get('user-id')
|
|
173
|
-
if (typeof userId !== 'string' || !userId) {
|
|
174
|
-
throw new Error('A user ID is required to resolve this sandbox')
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return userId
|
|
178
|
-
}
|
|
179
|
-
|
|
180
164
|
const workspace = new Workspace({
|
|
181
165
|
sandbox: async ({ requestContext }) => {
|
|
182
|
-
const
|
|
166
|
+
const userId = requestContext.get('user-id') as string
|
|
167
|
+
const sandbox = new DaytonaSandbox({ id: `user-${userId}` })
|
|
183
168
|
await sandbox.start()
|
|
184
169
|
return sandbox
|
|
185
170
|
},
|
|
186
|
-
sandboxCacheKey: ({ requestContext }) =>
|
|
171
|
+
sandboxCacheKey: ({ requestContext }) => requestContext.get('user-id') as string,
|
|
187
172
|
})
|
|
188
173
|
```
|
|
189
174
|
|
|
190
175
|
The first request from a user creates the sandbox. Later requests with the same user ID reuse it.
|
|
191
176
|
|
|
192
|
-
For a sandbox with persistent storage, create
|
|
177
|
+
For a sandbox with persistent storage, create a [filesystem](https://mastra.ai/docs/sandbox/filesystem) and mount it inside the resolver. This example creates one Daytona sandbox and one S3 storage prefix per memory thread, then mounts the storage at `/workspace`:
|
|
193
178
|
|
|
194
179
|
```typescript
|
|
195
180
|
import { MASTRA_THREAD_ID_KEY, type RequestContext } from '@mastra/core/request-context'
|
|
@@ -197,14 +182,8 @@ import { Workspace } from '@mastra/core/workspace'
|
|
|
197
182
|
import { DaytonaSandbox } from '@mastra/daytona'
|
|
198
183
|
import { S3Filesystem } from '@mastra/s3'
|
|
199
184
|
|
|
200
|
-
const getThreadId = (requestContext: RequestContext) =>
|
|
201
|
-
|
|
202
|
-
if (typeof threadId !== 'string' || !threadId) {
|
|
203
|
-
throw new Error('A memory thread is required to resolve this sandbox')
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return threadId
|
|
207
|
-
}
|
|
185
|
+
const getThreadId = (requestContext: RequestContext) =>
|
|
186
|
+
requestContext.get(MASTRA_THREAD_ID_KEY) as string
|
|
208
187
|
|
|
209
188
|
const createThreadFilesystem = (threadId: string) =>
|
|
210
189
|
new S3Filesystem({
|
|
@@ -234,25 +213,13 @@ The first request in a thread runs the resolver and starts the sandbox. Later re
|
|
|
234
213
|
|
|
235
214
|
The example mounts the filesystem inside the resolver because static `mounts` can't be combined with a sandbox resolver. Generated tools resolve the filesystem and sandbox from the request context automatically.
|
|
236
215
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
You are responsible for every sandbox returned by a resolver. It must be ready to use when returned. Keep track of it and destroy it through your application lifecycle code when it's no longer needed. `workspace.destroy()` doesn't destroy resolver-returned sandboxes.
|
|
240
|
-
|
|
241
|
-
Resolvers are incompatible with `mounts` and [`lsp: true`](https://mastra.ai/docs/sandbox/lsp), because both require a static sandbox during configuration. Using a resolver with `mounts` throws an `INVALID_CONFIG` error. With `lsp: true`, Mastra disables LSP and logs a warning.
|
|
216
|
+
> **Warning:** Your application owns sandboxes returned by a resolver. Destroy them and call `workspace.clearSandboxCache(cacheKey)` when the user, thread, or session ends. `workspace.destroy()` doesn't destroy resolver-returned sandboxes.
|
|
242
217
|
|
|
243
218
|
### Tool availability
|
|
244
219
|
|
|
245
|
-
With a static sandbox, Mastra knows which capabilities the backend supports and only gives the agent the corresponding tools. Application code should also check that an optional capability is available before using it
|
|
246
|
-
|
|
247
|
-
```typescript
|
|
248
|
-
const processManager = workspace.sandbox?.processes
|
|
249
|
-
|
|
250
|
-
if (processManager) {
|
|
251
|
-
await processManager.spawn('pnpm dev')
|
|
252
|
-
}
|
|
253
|
-
```
|
|
220
|
+
With a static sandbox, Mastra knows which capabilities the backend supports and only gives the agent the corresponding tools. Application code should also check that an optional capability is available before using it.
|
|
254
221
|
|
|
255
|
-
With a [resolver-backed sandbox](#
|
|
222
|
+
With a [resolver-backed sandbox](#sandboxes-per-user-or-thread), the backend isn't known until a request runs, so Mastra initially makes all sandbox tools available. If the resolved backend doesn't support the tool the agent calls, the call fails with `SandboxFeatureNotSupportedError`.
|
|
256
223
|
|
|
257
224
|
## Background processes
|
|
258
225
|
|
|
@@ -292,7 +259,7 @@ Who shares a sandbox depends on where you configure it and whether you use a res
|
|
|
292
259
|
| Resource-scoped resolver | The resolver caches one sandbox for each resource ID. |
|
|
293
260
|
| Thread-scoped resolver | A memory thread keeps its sandbox across requests in that thread. |
|
|
294
261
|
|
|
295
|
-
A static sandbox isn't automatically scoped to the current resource or memory thread. For resource or thread scope, use a resolver and set `sandboxCacheKey` to the corresponding ID. See [
|
|
262
|
+
A static sandbox isn't automatically scoped to the current resource or memory thread. For resource or thread scope, use a resolver and set `sandboxCacheKey` to the corresponding ID. See [Sandboxes per user or thread](#sandboxes-per-user-or-thread).
|
|
296
263
|
|
|
297
264
|
### Start
|
|
298
265
|
|
|
@@ -6,6 +6,10 @@ Mastra servers can execute custom middleware functions before or after an API ro
|
|
|
6
6
|
|
|
7
7
|
A middleware receives the [Hono](https://hono.dev) `Context` (`c`) and a `next` function. If it returns a `Response` the request is short-circuited. Calling `next()` continues processing the next middleware or route handler.
|
|
8
8
|
|
|
9
|
+
> **Note:** Middleware handlers use Hono's signature, so they run on Hono-based serving paths: `mastra dev` and `mastra build`, the [Hono adapter](https://mastra.ai/reference/server/hono-adapter), and adapters built on it such as `@mastra/next` and `@mastra/tanstack-start`. Adapters for other frameworks (Express, Fastify, Koa) can't run Hono handlers and log a warning at startup when middleware is configured. Register middleware through the framework's own API there instead.
|
|
10
|
+
>
|
|
11
|
+
> User middleware never runs on routes declared public with `requiresAuth: false`, so it can't block endpoints the framework needs to keep reachable, such as the Studio sign-in routes.
|
|
12
|
+
|
|
9
13
|
```typescript
|
|
10
14
|
import { Mastra } from '@mastra/core'
|
|
11
15
|
|
|
@@ -424,11 +424,12 @@ See the [NestJS Adapter](https://mastra.ai/reference/server/nestjs-adapter) docu
|
|
|
424
424
|
|
|
425
425
|
## Initialization flow
|
|
426
426
|
|
|
427
|
-
Calling `init()` runs
|
|
427
|
+
Calling `init()` runs four steps in order. Understanding this flow helps when you need to insert your own middleware at specific points.
|
|
428
428
|
|
|
429
429
|
1. `registerContextMiddleware()`: Attaches the Mastra instance, request context, tools, and abort signal to every request. This makes Mastra available to all subsequent middleware and route handlers.
|
|
430
430
|
2. `registerAuthMiddleware()`: Runs the adapter auth hook during initialization. Official adapters enforce auth inline when Mastra registers built-in routes and `registerApiRoute()` routes, so raw framework routes should use the adapter's exported `createAuthMiddleware()` helper when they need Mastra auth.
|
|
431
|
-
3. `
|
|
431
|
+
3. `registerUserMiddleware()`: Registers [middleware](https://mastra.ai/docs/server/middleware) from the `server.middleware` config and `mastra.setServerMiddleware()`. Middleware handlers use Hono's signature, so Hono-based adapters (`@mastra/hono` and the adapters built on it) mount them; other adapters log a warning instead of silently ignoring the config.
|
|
432
|
+
4. `registerRoutes()`: Registers all Mastra API routes for agents, workflows, and other features. Also registers MCP routes if MCP servers are configured.
|
|
432
433
|
|
|
433
434
|
### Manual initialization
|
|
434
435
|
|
|
@@ -445,6 +446,9 @@ server.registerContextMiddleware();
|
|
|
445
446
|
// Middleware that needs Mastra context
|
|
446
447
|
app.use(customMiddleware);
|
|
447
448
|
|
|
449
|
+
// Registers `server.middleware` and `setServerMiddleware()` middleware
|
|
450
|
+
server.registerUserMiddleware();
|
|
451
|
+
|
|
448
452
|
await server.registerRoutes();
|
|
449
453
|
|
|
450
454
|
// Routes after Mastra
|
|
@@ -566,11 +570,12 @@ When using server adapters, configuration comes from two places: the Mastra `ser
|
|
|
566
570
|
|
|
567
571
|
The adapter reads these settings from `mastra.getServer()`:
|
|
568
572
|
|
|
569
|
-
| Option | Description
|
|
570
|
-
| --------------- |
|
|
571
|
-
| `auth` | Authentication config, used by `registerAuthMiddleware()`.
|
|
572
|
-
| `bodySizeLimit` | Default body size limit in bytes. Can be overridden per-adapter via `bodyLimitOptions`.
|
|
573
|
-
| `onError` | Custom error handler called when an unhandled error occurs in a route handler. See [server.onError](https://mastra.ai/reference/configuration).
|
|
573
|
+
| Option | Description |
|
|
574
|
+
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
575
|
+
| `auth` | Authentication config, used by `registerAuthMiddleware()`. |
|
|
576
|
+
| `bodySizeLimit` | Default body size limit in bytes. Can be overridden per-adapter via `bodyLimitOptions`. |
|
|
577
|
+
| `onError` | Custom error handler called when an unhandled error occurs in a route handler. See [server.onError](https://mastra.ai/reference/configuration). |
|
|
578
|
+
| `middleware` | User [middleware](https://mastra.ai/docs/server/middleware), registered by `registerUserMiddleware()`. Hono-based adapters run it; Express, Fastify, and Koa log a warning because Hono handlers can't run there. |
|
|
574
579
|
|
|
575
580
|
### Adapter constructor only
|
|
576
581
|
|
|
@@ -595,7 +600,6 @@ These `server` config options are only used by `mastra build` and have no effect
|
|
|
595
600
|
| `cors` | `mastra build` adds CORS middleware |
|
|
596
601
|
| `timeout` | `mastra build` |
|
|
597
602
|
| `apiRoutes` | `registerApiRoute()` for `mastra build` |
|
|
598
|
-
| `middleware` | Middleware config for `mastra build` |
|
|
599
603
|
|
|
600
604
|
When using adapters, configure these features directly with your framework. For example, add CORS middleware using Hono's or Express's built-in CORS packages, and set the port when calling your framework's listen function.
|
|
601
605
|
|
package/.docs/docs/storage.md
CHANGED
|
@@ -197,6 +197,7 @@ Each provider page includes installation instructions, configuration parameters,
|
|
|
197
197
|
- [Convex](https://mastra.ai/integrations/databases/convex)
|
|
198
198
|
- [DuckDB](https://mastra.ai/integrations/databases/duckdb)
|
|
199
199
|
- [DynamoDB](https://mastra.ai/integrations/databases/dynamodb)
|
|
200
|
+
- [Elasticsearch](https://mastra.ai/integrations/databases/elasticsearch)
|
|
200
201
|
- [Google Cloud Spanner](https://mastra.ai/integrations/databases/spanner)
|
|
201
202
|
- [LanceDB](https://mastra.ai/integrations/databases/lancedb)
|
|
202
203
|
- [libSQL](https://mastra.ai/integrations/databases/libsql)
|
|
@@ -207,6 +208,7 @@ Each provider page includes installation instructions, configuration parameters,
|
|
|
207
208
|
- [OracleDB](https://mastra.ai/integrations/databases/oracledb)
|
|
208
209
|
- [PostgreSQL](https://mastra.ai/integrations/databases/postgresql)
|
|
209
210
|
- [Redis](https://mastra.ai/integrations/databases/redis)
|
|
211
|
+
- [Valkey](https://mastra.ai/integrations/databases/valkey)
|
|
210
212
|
- [Upstash](https://mastra.ai/integrations/databases/upstash)
|
|
211
213
|
|
|
212
214
|
## Next steps
|
|
@@ -2,10 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
# iMessage
|
|
4
4
|
|
|
5
|
-
iMessage channels let a Mastra agent receive direct messages and group messages from iMessage. Mastra handles the agent wiring
|
|
5
|
+
iMessage channels let a Mastra agent receive direct messages and group messages from iMessage. Two vendor-maintained adapters connect Mastra to iMessage: [Photon](https://app.photon.codes) and [Linq](https://linqapp.com). Mastra handles the agent wiring and the webhook route; the adapter docs cover number provisioning, credentials, and webhook registration.
|
|
6
|
+
|
|
7
|
+
## Choose an adapter
|
|
8
|
+
|
|
9
|
+
Both adapters follow the same Mastra wiring, so pick the provider first:
|
|
10
|
+
|
|
11
|
+
- **Photon**: Hosted or self-hosted iMessage service. Supports webhooks and a [gateway listener](#gateway-listener) that streams messages over an open connection.
|
|
12
|
+
- **Linq**: Hosted iMessage, RCS, and SMS API. Webhook-driven, with tapback reactions and media mapped in both directions.
|
|
6
13
|
|
|
7
14
|
## Install the adapter
|
|
8
15
|
|
|
16
|
+
**Photon**:
|
|
17
|
+
|
|
9
18
|
Install the Photon iMessage adapter:
|
|
10
19
|
|
|
11
20
|
**npm**:
|
|
@@ -32,9 +41,87 @@ yarn add @photon-ai/chat-adapter-imessage
|
|
|
32
41
|
bun add @photon-ai/chat-adapter-imessage
|
|
33
42
|
```
|
|
34
43
|
|
|
44
|
+
**Linq**:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install @photon-ai/chat-adapter-imessage
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Tab 3**:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pnpm add @photon-ai/chat-adapter-imessage
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Tab 4**:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
yarn add @photon-ai/chat-adapter-imessage
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Tab 5**:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
bun add @photon-ai/chat-adapter-imessage
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Tab 6**:
|
|
69
|
+
|
|
70
|
+
Install the Linq Chat SDK adapter:
|
|
71
|
+
|
|
72
|
+
**npm**:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install @linqapp/chat-sdk-adapter
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**pnpm**:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pnpm add @linqapp/chat-sdk-adapter
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Yarn**:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
yarn add @linqapp/chat-sdk-adapter
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Bun**:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
bun add @linqapp/chat-sdk-adapter
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Tab 7**:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npm install @linqapp/chat-sdk-adapter
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Tab 8**:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pnpm add @linqapp/chat-sdk-adapter
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Tab 9**:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
yarn add @linqapp/chat-sdk-adapter
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Tab 10**:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
bun add @linqapp/chat-sdk-adapter
|
|
118
|
+
```
|
|
119
|
+
|
|
35
120
|
## Agent configuration
|
|
36
121
|
|
|
37
|
-
Add
|
|
122
|
+
Add the adapter factory to the agent's `channels.adapters` object:
|
|
123
|
+
|
|
124
|
+
**Photon**:
|
|
38
125
|
|
|
39
126
|
```typescript
|
|
40
127
|
import { Agent } from '@mastra/core/agent'
|
|
@@ -57,6 +144,35 @@ export const imessageAgent = new Agent({
|
|
|
57
144
|
})
|
|
58
145
|
```
|
|
59
146
|
|
|
147
|
+
`threadContext: { maxMessages: 0 }` skips the platform history lookup Mastra runs when an agent is first mentioned in a group chat, which the Photon adapter can't perform.
|
|
148
|
+
|
|
149
|
+
**Linq**:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import { Agent } from '@mastra/core/agent'
|
|
153
|
+
import { createLinqAdapter } from '@linqapp/chat-sdk-adapter'
|
|
154
|
+
|
|
155
|
+
export const imessageAgent = new Agent({
|
|
156
|
+
id: 'imessage-agent',
|
|
157
|
+
name: 'iMessage Agent',
|
|
158
|
+
instructions: 'Answer questions and help with tasks over iMessage.',
|
|
159
|
+
model: 'openai/gpt-5.6-sol',
|
|
160
|
+
channels: {
|
|
161
|
+
adapters: {
|
|
162
|
+
imessage: {
|
|
163
|
+
adapter: createLinqAdapter({
|
|
164
|
+
apiKey: process.env.LINQ_API_KEY!,
|
|
165
|
+
signingSecret: process.env.LINQ_WEBHOOK_SECRET!,
|
|
166
|
+
}),
|
|
167
|
+
toolDisplay: 'text',
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
})
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`createLinqAdapter()` takes the credentials directly: `apiKey` is your Linq API key and `signingSecret` comes from the [webhook subscription](#webhook-url). The Linq adapter can fetch thread history, so the default `threadContext` behavior works.
|
|
175
|
+
|
|
60
176
|
Register the agent on the Mastra instance:
|
|
61
177
|
|
|
62
178
|
```typescript
|
|
@@ -70,10 +186,14 @@ export const mastra = new Mastra({
|
|
|
70
186
|
|
|
71
187
|
Use `imessage` as the adapter key. Mastra derives the webhook path and the `platform` value on `requestContext` from this key.
|
|
72
188
|
|
|
73
|
-
`toolDisplay: 'text'` describes tool calls in the message, since iMessage has no interactive cards for Approve and Deny actions.
|
|
189
|
+
`toolDisplay: 'text'` describes tool calls in the message, since iMessage has no interactive cards for Approve and Deny actions. Both adapters need it: Photon has no card rendering, and Linq flattens cards to plain text where buttons show their labels but can't trigger actions.
|
|
190
|
+
|
|
191
|
+
> **Warning:** Text rendering can't submit approval decisions. A tool with `requireApproval: true` stays suspended until a UI or API action, such as Studio, submits an explicit approval or decline, so avoid approval-gated tools on iMessage agents unless another surface handles the decision. See [Tool approval](https://mastra.ai/docs/channels).
|
|
74
192
|
|
|
75
193
|
## Adapter setup
|
|
76
194
|
|
|
195
|
+
**Photon**:
|
|
196
|
+
|
|
77
197
|
Follow the [Photon iMessage adapter docs](https://github.com/photon-hq/vercel-chat-adapter-imessage) for iMessage-specific setup, including number provisioning, hosted and self-hosted modes, and webhook registration. The adapter picks its mode from the environment variables you set.
|
|
78
198
|
|
|
79
199
|
For the hosted service, create a project at [app.photon.codes](https://app.photon.codes) and use the project credentials:
|
|
@@ -94,6 +214,17 @@ IMESSAGE_PHONE=+15551234567
|
|
|
94
214
|
|
|
95
215
|
`IMESSAGE_PHONE` is optional and routes messages when a self-hosted server has several numbers. You can also pass these values to `createiMessageAdapter()` directly, including a `credentials` function that resolves the project ID and secret at first use from a secret store.
|
|
96
216
|
|
|
217
|
+
**Linq**:
|
|
218
|
+
|
|
219
|
+
Follow the [Linq API docs](https://docs.linqapp.com/) for Linq-specific setup, including phone number provisioning and API keys. Create a Linq account, copy your API key, and set the credentials the agent configuration reads:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
LINQ_API_KEY=your-linq-api-key
|
|
223
|
+
LINQ_WEBHOOK_SECRET=your-webhook-signing-secret
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`LINQ_WEBHOOK_SECRET` is the signing secret returned when you create a webhook subscription in the next section. The adapter also accepts a `baseURL` option to target a different Linq API base URL, such as a sandbox.
|
|
227
|
+
|
|
97
228
|
## Webhook URL
|
|
98
229
|
|
|
99
230
|
Mastra generates the iMessage webhook route from the agent ID and adapter key:
|
|
@@ -108,13 +239,25 @@ Use your public Mastra server URL as the base URL:
|
|
|
108
239
|
https://your-app.example.com/api/agents/imessage-agent/channels/imessage/webhook
|
|
109
240
|
```
|
|
110
241
|
|
|
242
|
+
**Photon**:
|
|
243
|
+
|
|
111
244
|
Register this URL in the [Photon dashboard](https://app.photon.codes), then set the signing secret it returns as `IMESSAGE_WEBHOOK_SECRET`. The secret is shown once at registration. The adapter verifies the signature on every delivery and rejects requests that don't match. Webhooks are available in hosted mode only.
|
|
112
245
|
|
|
113
|
-
|
|
246
|
+
**Linq**:
|
|
247
|
+
|
|
248
|
+
Create a [webhook subscription](https://docs.linqapp.com/guides/webhooks/subscriptions/) with this URL as the target and subscribe to at least these events:
|
|
249
|
+
|
|
250
|
+
- `message.received`
|
|
251
|
+
- `reaction.added`
|
|
252
|
+
- `reaction.removed`
|
|
253
|
+
|
|
254
|
+
Set the `signing_secret` the subscription returns as `LINQ_WEBHOOK_SECRET`. The secret is shown once at creation and can't be retrieved later. The adapter verifies the HMAC signature on every delivery, checks for replayed requests, and rejects requests that don't match.
|
|
255
|
+
|
|
256
|
+
> **Note:** Both providers deliver to public HTTPS endpoints only, not to `http://` or private addresses like `localhost`. For local development, use a tunnel as described in the [Channels overview](https://mastra.ai/docs/channels).
|
|
114
257
|
|
|
115
258
|
## Duplicate deliveries
|
|
116
259
|
|
|
117
|
-
Photon
|
|
260
|
+
Photon and Linq retry failed deliveries with backoff and deliver at least once, so the same message can arrive twice. Chat SDK drops the repeat using the channel state adapter, and Mastra's default keeps those dedup keys in memory. That covers a single long-running server.
|
|
118
261
|
|
|
119
262
|
A repeat can still reach the agent after a restart, or on serverless where the retry is routed to a different instance. Pass a shared state adapter on `channels.state` so dedup keys are visible everywhere. Install one alongside the adapter:
|
|
120
263
|
|
|
@@ -154,7 +297,6 @@ channels: {
|
|
|
154
297
|
toolDisplay: 'text',
|
|
155
298
|
},
|
|
156
299
|
},
|
|
157
|
-
threadContext: { maxMessages: 0 },
|
|
158
300
|
state: createRedisState(),
|
|
159
301
|
},
|
|
160
302
|
```
|
|
@@ -163,13 +305,13 @@ This matters most for tools with side effects, where handling the same message t
|
|
|
163
305
|
|
|
164
306
|
## Read receipts
|
|
165
307
|
|
|
166
|
-
iPhone Messages sends a read receipt for every message the agent posts, and the adapter delivers those receipts as inbound messages with no text and no attachments. Mastra skips them, so the agent doesn't answer its own reply in a loop. Their message IDs carry a `:read:` suffix, and they show up as skipped messages at `debug` log level.
|
|
308
|
+
iPhone Messages sends a read receipt for every message the agent posts, and the Photon adapter delivers those receipts as inbound messages with no text and no attachments. Mastra skips them, so the agent doesn't answer its own reply in a loop. Their message IDs carry a `:read:` suffix, and they show up as skipped messages at `debug` log level.
|
|
167
309
|
|
|
168
310
|
The same rule applies to every adapter: an inbound message with neither text nor attachments never starts an agent run. A custom `onDirectMessage`, `onMention`, or `onSubscribedMessage` handler still receives it and can act on it before calling `defaultHandler`.
|
|
169
311
|
|
|
170
312
|
## Gateway listener
|
|
171
313
|
|
|
172
|
-
The adapter can hold an open connection and stream messages in real time instead of receiving webhooks. This works in both hosted and self-hosted modes.
|
|
314
|
+
The Photon adapter can hold an open connection and stream messages in real time instead of receiving webhooks. This works in both hosted and self-hosted modes. The Linq adapter is webhook-driven and has no gateway mode.
|
|
173
315
|
|
|
174
316
|
Mastra starts this listener during initialization and reconnects it if it drops, so no cron job or extra route is needed on a long-running server. Set `gateway: false` on the adapter config to turn it off when you use webhooks:
|
|
175
317
|
|