@mastra/mcp-docs-server 1.2.18-alpha.1 → 1.2.18-alpha.5

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.
Files changed (36) hide show
  1. package/.docs/docs/mastra-platform/workspaces.md +6 -3
  2. package/.docs/docs/sandbox/filesystem.md +120 -139
  3. package/.docs/docs/sandbox/lsp.md +195 -143
  4. package/.docs/docs/sandbox/overview.md +103 -69
  5. package/.docs/docs/sandbox/search.md +172 -153
  6. package/.docs/docs/sandbox/skills.md +94 -151
  7. package/.docs/integrations/agentic-ui/ai-sdk-ui.md +2 -1
  8. package/.docs/integrations/deploy/render.md +136 -89
  9. package/.docs/integrations/frameworks/next-js.md +3 -2
  10. package/.docs/models/gateways/netlify.md +227 -70
  11. package/.docs/models/gateways/openrouter.md +3 -4
  12. package/.docs/models/gateways/vercel.md +5 -3
  13. package/.docs/models/index.md +1 -1
  14. package/.docs/models/providers/crossmodel.md +3 -2
  15. package/.docs/models/providers/edenai.md +4 -7
  16. package/.docs/models/providers/empiriolabs.md +3 -2
  17. package/.docs/models/providers/huggingface.md +2 -1
  18. package/.docs/models/providers/hyper.md +9 -9
  19. package/.docs/models/providers/inceptron.md +4 -5
  20. package/.docs/models/providers/kilo.md +10 -11
  21. package/.docs/models/providers/llmgateway.md +4 -3
  22. package/.docs/models/providers/nano-gpt.md +3 -1
  23. package/.docs/models/providers/ofox.md +5 -5
  24. package/.docs/models/providers/opencode-go.md +1 -1
  25. package/.docs/models/providers/opencode.md +65 -65
  26. package/.docs/models/providers/pioneer.md +12 -12
  27. package/.docs/models/providers/runinfra.md +2 -1
  28. package/.docs/models/providers/umans-ai-coding-plan.md +1 -2
  29. package/.docs/models/providers/umans-ai.md +1 -2
  30. package/.docs/reference/agent-controller/agent-controller-class.md +1 -1
  31. package/.docs/reference/ai-sdk/handle-chat-stream.md +2 -1
  32. package/.docs/reference/observability/tracing/exporters/langfuse.md +2 -0
  33. package/.docs/reference/rag/metadata-filters.md +16 -8
  34. package/.docs/reference/rag/retrieval.md +113 -5
  35. package/CHANGELOG.md +14 -0
  36. package/package.json +5 -5
@@ -1,30 +1,28 @@
1
1
  > Discover all available pages from the documentation index: https://mastra.ai/llms.txt
2
2
 
3
- # Sandbox
3
+ # Sandboxes and filesystems
4
4
 
5
- Sandboxes give Mastra agents an environment for running commands and working with files. Agents can also start background processes when the selected backend supports process management; otherwise, background-process tools are unavailable.
5
+ A sandbox gives your agent an environment where it can run commands, execute code, install dependencies, and manage processes. Sandboxes can isolate potentially risky agent-run code from your host, and they can also provide a place to run longer-lived or more resource-intensive work outside your application process.
6
6
 
7
- In Mastra, you attach a sandbox backend to a `Workspace`. You can use one sandbox for an application or agent, or resolve separate environments for each user, tenant, or thread.
7
+ [Filesystems](https://mastra.ai/docs/sandbox/filesystem) give the agent files it can read, write, and [search](https://mastra.ai/docs/sandbox/search). They can provide a knowledge base for your agent, or be shared with a sandbox so commands can work with the same files and keep data beyond the sandbox's lifetime.
8
8
 
9
- Supported backends can FUSE-mount filesystems into the sandbox, letting you seed ephemeral environments with files and persist their output between runs.
9
+ ## When to use sandboxes
10
10
 
11
- ## When to use sandboxes?
12
-
13
- Sandboxes are useful for:
11
+ Sandboxes work well for:
14
12
 
15
13
  - **Coding agents and software factories**: Clone repositories and run shell, Git, build, or test workflows without giving autonomous agents access to the host system.
16
14
  - **Deep research and analysis**: Use specialized libraries to process downloaded PDFs or presentations and produce new artifacts.
17
15
  - **Long-running and parallel tasks**: Run work in separate environments without tying it to one request or sharing files and processes.
18
16
 
19
- ## Quickstart
17
+ If an agent only needs to read, write, or search files, configure [direct filesystem access](https://mastra.ai/docs/sandbox/filesystem).
20
18
 
21
- In Mastra, a sandbox operates through a `Workspace`. Its `sandbox` backend defines where commands run.
19
+ ## Quickstart
22
20
 
23
- Create an agent with a workspace and sandbox:
21
+ Give an agent a local sandbox and filesystem:
24
22
 
25
23
  ```typescript
26
24
  import { Agent } from '@mastra/core/agent'
27
- import { LocalSandbox, Workspace } from '@mastra/core/workspace'
25
+ import { LocalFilesystem, LocalSandbox, Workspace } from '@mastra/core/workspace'
28
26
 
29
27
  export const codingAgent = new Agent({
30
28
  id: 'coding-agent',
@@ -35,16 +33,21 @@ export const codingAgent = new Agent({
35
33
  sandbox: new LocalSandbox({
36
34
  workingDirectory: './workspace',
37
35
  }),
36
+ filesystem: new LocalFilesystem({
37
+ basePath: './workspace',
38
+ }),
38
39
  }),
39
40
  })
40
41
 
41
- await codingAgent.generate('List the files in the workspace directory')
42
+ await codingAgent.generate('List the files in the sandbox directory')
42
43
  ```
43
44
 
44
45
  > **Warning:** `LocalSandbox` is the quickest sandbox to set up, but commands run on the host by default. Enable [native isolation](#localsandbox) whenever possible. For applications exposed to untrusted users, use a remote or container backend with a stronger isolation boundary instead.
45
46
 
46
47
  To use a different sandbox for each user, tenant, or thread, configure a [sandbox resolver](#multi-tenant-sandboxes) instead.
47
48
 
49
+ The `LocalSandbox` above is one static instance. Every request and memory thread that uses `codingAgent` shares its files, processes, and lifecycle state. A memory thread doesn't create an isolated sandbox by itself.
50
+
48
51
  ## Using the sandbox
49
52
 
50
53
  Agents receive tools for the capabilities supported by the sandbox backend:
@@ -55,12 +58,29 @@ Agents receive tools for the capabilities supported by the sandbox backend:
55
58
  | `get_process_output` | Gets stdout, stderr, and status for a background process. Supports `tail` to limit output and `wait: true` to wait for the process to exit. This tool and `kill_process` are only added when the backend supports background processes. |
56
59
  | `kill_process` | Stops a background process and returns its recent output. |
57
60
 
58
- You can disable or rename each tool and require approval before it runs. You can also limit how much output it adds to the model's context. See the [workspace tool configuration reference](https://mastra.ai/reference/workspace/workspace-class).
61
+ Configured capabilities determine which tools are available. Tool configuration can then disable, rename, or add policies to those tools:
62
+
63
+ ```typescript
64
+ import { LocalSandbox, Workspace, WORKSPACE_TOOLS } from '@mastra/core/workspace'
65
+
66
+ const workspace = new Workspace({
67
+ sandbox: new LocalSandbox({ workingDirectory: './workspace' }),
68
+ tools: {
69
+ requireApproval: true,
70
+ [WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND]: {
71
+ enabled: false,
72
+ },
73
+ },
74
+ })
75
+ ```
76
+
77
+ Omitting a tool entry keeps its capability-driven default. 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
+
79
+ 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.
59
80
 
60
- Authored runtime functions, including tools and workflow steps, can get the live sandbox from their execution context. Start a server, watcher, notebook kernel, browser, or worker and interact with it across agent turns.
81
+ Authored runtime functions, including tools and workflow steps, can get the live sandbox from their execution context. Use it to execute commands, install dependencies, process files, or spawn a long-running process.
61
82
 
62
83
  ```typescript
63
- import type { ProcessHandle } from '@mastra/core/sandbox'
64
84
  import { createTool } from '@mastra/core/tools'
65
85
  import { z } from 'zod'
66
86
 
@@ -71,15 +91,24 @@ export const startServerTool = createTool({
71
91
  command: z.string(),
72
92
  }),
73
93
  execute: async ({ command }, ctx) => {
74
- const sandbox = await ctx.getSandbox()
75
- const server: ProcessHandle = await sandbox.spawn({ command })
94
+ if (!ctx.workspace || !ctx.requestContext) {
95
+ throw new Error('This tool requires a workspace execution context')
96
+ }
97
+
98
+ const sandbox = await ctx.workspace.resolveSandbox({
99
+ requestContext: ctx.requestContext,
100
+ })
101
+
102
+ if (!sandbox?.processes) {
103
+ throw new Error('The configured sandbox does not support background processes')
104
+ }
105
+
106
+ const server = await sandbox.processes.spawn(command)
76
107
  return { pid: server.pid }
77
108
  },
78
109
  })
79
110
  ```
80
111
 
81
- The authored function itself runs in your application process. Only operations called through the sandbox handle run inside the sandbox.
82
-
83
112
  ## Supported backends
84
113
 
85
114
  ### `LocalSandbox`
@@ -102,11 +131,11 @@ const sandbox = new LocalSandbox({
102
131
  })
103
132
  ```
104
133
 
105
- Use `LocalSandbox.detectIsolation()` to check whether Seatbelt or Bubblewrap is available on the current operating system. The `nativeSandbox` options control network access, read-only or writable paths, workspace write access, 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.
134
+ Use `LocalSandbox.detectIsolation()` to check whether Seatbelt or Bubblewrap is available on the current operating system. 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.
106
135
 
107
136
  ### Other backends
108
137
 
109
- Use a remote or container backend when commands need a stronger boundary from the host application. Each backend has its own isolation, persistence, networking, and mount behavior:
138
+ Use a remote or container backend when commands need a stronger boundary from the host application or when workloads need to scale beyond the resources of your application server. Each backend has its own isolation, persistence, networking, and mount behavior:
110
139
 
111
140
  - [AgentCore](https://mastra.ai/integrations/sandboxes/agentcore)
112
141
  - [Apple Container](https://mastra.ai/integrations/sandboxes/apple-container)
@@ -120,39 +149,47 @@ Use a remote or container backend when commands need a stronger boundary from th
120
149
  - [Railway](https://mastra.ai/integrations/sandboxes/railway)
121
150
  - [Vercel](https://mastra.ai/integrations/sandboxes/vercel)
122
151
 
123
- If Mastra doesn't support your execution backend, implement [`WorkspaceSandbox`](https://mastra.ai/reference/workspace/sandbox) to add it.
152
+ If Mastra doesn't support your execution backend, implement the [sandbox provider interface](https://mastra.ai/reference/workspace/sandbox) to add it.
153
+
154
+ ## Filesystem
124
155
 
125
- ## Persistent storage
156
+ Each sandbox has a native filesystem that commands can use. Treat it as ephemeral because its contents follow the sandbox's lifecycle.
126
157
 
127
- Some execution backends can also mount cloud filesystems through FUSE. Mounted files appear as local directories inside the sandbox, so commands such as `cat /data/report.md` or `python /data/analyze.py` can use them directly.
158
+ To seed a sandbox with files or keep files between runs, give the agent a [filesystem](https://mastra.ai/docs/sandbox/filesystem). Use mounts when sandbox commands need access to the same files.
128
159
 
129
- Configure mounts on the workspace alongside the sandbox:
160
+ ## Multi-tenant sandboxes
161
+
162
+ 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
+
164
+ This example creates and caches one Daytona sandbox per user:
130
165
 
131
166
  ```typescript
167
+ import type { RequestContext } from '@mastra/core/request-context'
132
168
  import { Workspace } from '@mastra/core/workspace'
133
169
  import { DaytonaSandbox } from '@mastra/daytona'
134
- import { S3Filesystem } from '@mastra/s3'
170
+
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
+ }
135
179
 
136
180
  const workspace = new Workspace({
137
- mounts: {
138
- '/data': new S3Filesystem({
139
- bucket: 'agent-data',
140
- region: 'us-east-1',
141
- }),
181
+ sandbox: async ({ requestContext }) => {
182
+ const sandbox = new DaytonaSandbox({ id: `user-${getUserId(requestContext)}` })
183
+ await sandbox.start()
184
+ return sandbox
142
185
  },
143
- sandbox: new DaytonaSandbox(),
186
+ sandboxCacheKey: ({ requestContext }) => getUserId(requestContext),
144
187
  })
145
188
  ```
146
189
 
147
- The agent receives filesystem tools for the mounted storage, and commands in the sandbox can access the same files under `/data`. This lets you seed an ephemeral sandbox with existing files and persist its output after the sandbox stops.
148
-
149
- Mount support varies by sandbox backend. See [Filesystem](https://mastra.ai/docs/sandbox/filesystem) for supported storage backends, detailed mount configuration, and how to mount multiple filesystems.
150
-
151
- ## Multi-tenant sandboxes
152
-
153
- 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.
190
+ The first request from a user creates the sandbox. Later requests with the same user ID reuse it.
154
191
 
155
- This example creates one Daytona sandbox and one S3 storage prefix per memory thread. The S3 filesystem is also mounted at `/workspace` inside the sandbox:
192
+ For a sandbox with persistent storage, create the 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`:
156
193
 
157
194
  ```typescript
158
195
  import { MASTRA_THREAD_ID_KEY, type RequestContext } from '@mastra/core/request-context'
@@ -163,7 +200,7 @@ import { S3Filesystem } from '@mastra/s3'
163
200
  const getThreadId = (requestContext: RequestContext) => {
164
201
  const threadId = requestContext.get(MASTRA_THREAD_ID_KEY)
165
202
  if (typeof threadId !== 'string' || !threadId) {
166
- throw new Error('A memory thread is required to use this workspace')
203
+ throw new Error('A memory thread is required to resolve this sandbox')
167
204
  }
168
205
 
169
206
  return threadId
@@ -195,32 +232,27 @@ const workspace = new Workspace({
195
232
 
196
233
  The first request in a thread runs the resolver and starts the sandbox. Later requests with the same thread ID reuse the cached sandbox. A different thread ID creates a different sandbox and storage prefix.
197
234
 
198
- Workspace-level `mounts` can't be combined with a sandbox resolver. This is a known limitation, so the example mounts the filesystem inside the resolver instead. Workspace tools resolve the filesystem and sandbox from the request context automatically.
235
+ 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.
199
236
 
200
237
  ### Resolver ownership
201
238
 
202
- The workspace doesn't own sandboxes returned by a resolver. Return a sandbox that's ready to use, and destroy it through your application's lifecycle code when it's no longer needed. `workspace.destroy()` doesn't destroy resolver-returned sandboxes.
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.
203
240
 
204
- Resolvers are incompatible with `mounts` and [`lsp: true`](https://mastra.ai/docs/sandbox/lsp), because both require a static sandbox when the workspace is constructed. Using a resolver with `mounts` throws an `INVALID_CONFIG` error. With `lsp: true`, Mastra disables LSP and logs a warning.
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.
205
242
 
206
243
  ### Tool availability
207
244
 
208
- With a static sandbox, Mastra knows which capabilities the backend supports and only gives the agent the corresponding tools. With a resolver-backed sandbox, 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`.
209
-
210
- For example, this resolver returns a Daytona sandbox for development requests and an AgentCore sandbox for other requests:
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:
211
246
 
212
247
  ```typescript
213
- const workspace = new Workspace({
214
- sandbox: ({ requestContext }) =>
215
- requestContext.get('environment') === 'development'
216
- ? new DaytonaSandbox()
217
- : new AgentCoreRuntimeSandbox({
218
- agentRuntimeArn: process.env.AGENTCORE_RUNTIME_ARN!,
219
- }),
220
- })
248
+ const processManager = workspace.sandbox?.processes
249
+
250
+ if (processManager) {
251
+ await processManager.spawn('pnpm dev')
252
+ }
221
253
  ```
222
254
 
223
- Mastra exposes the process tools because `DaytonaSandbox` supports background processes. If a request resolves to `AgentCoreRuntimeSandbox`, one-shot commands still work, but calling `get_process_output` or `kill_process` fails with `SandboxFeatureNotSupportedError` because that backend doesn't support background processes.
255
+ With a [resolver-backed sandbox](#multi-tenant-sandboxes), 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`.
224
256
 
225
257
  ## Background processes
226
258
 
@@ -247,26 +279,26 @@ These callbacks fire for all background processes started by the agent through `
247
279
 
248
280
  By default, background processes inherit the agent's abort signal and stop when the agent disconnects. Set `abortSignal` to a custom signal, or use `null` or `false` when the process should continue after the request ends.
249
281
 
250
- For the full `SandboxProcessManager` API (spawning processes programmatically and reading output, plus sending stdin), see the [`SandboxProcessManager` reference](https://mastra.ai/reference/workspace/process-manager).
282
+ For the full `SandboxProcessManager` API, including programmatic process spawning, output, and standard input, see the [`SandboxProcessManager` reference](https://mastra.ai/reference/workspace/process-manager).
251
283
 
252
284
  ## Lifecycle and persistence
253
285
 
254
- Sandbox scope depends on where you assign the workspace and whether you use a resolver:
286
+ Who shares a sandbox depends on where you configure it and whether you use a resolver:
255
287
 
256
- | Configuration | Sandbox scope |
257
- | ------------------------ | -------------------------------------------------------------------------------- |
258
- | Mastra-level workspace | Agents that inherit the workspace from the Mastra instance use the same sandbox. |
259
- | Agent-level workspace | Every request handled by that agent instance uses the same sandbox. |
260
- | Resource-scoped resolver | The resolver caches one sandbox for each resource ID. |
261
- | Thread-scoped resolver | A memory thread keeps its sandbox across requests in that thread. |
288
+ | Configuration | Who shares the sandbox |
289
+ | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
290
+ | Mastra-level configuration | Agents that inherit the configuration from the [`Mastra`](https://mastra.ai/reference/configuration) instance use the same sandbox. |
291
+ | Agent-level configuration | Every request handled by that agent instance uses the same sandbox. |
292
+ | Resource-scoped resolver | The resolver caches one sandbox for each resource ID. |
293
+ | Thread-scoped resolver | A memory thread keeps its sandbox across requests in that thread. |
262
294
 
263
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 [Multi-tenant sandboxes](#multi-tenant-sandboxes).
264
296
 
265
297
  ### Start
266
298
 
267
- Static sandbox backends are instantiated with your application, but their execution environment usually starts lazily on the first command. Call `workspace.init()` when you want to provision it during application startup instead.
299
+ Static sandbox environments usually start when the first command runs, so most applications don't need to initialize them explicitly. Call `workspace.init()` during application startup only when you want to start a static sandbox and prepare its mounts before serving requests. This is useful when credential, network, or provider errors need to surface during startup instead of on the first command.
268
300
 
269
- Resolver-backed sandboxes aren't started by `workspace.init()` because no backend is selected until the resolver runs. Return a sandbox that's already started or can start itself on first use.
301
+ `workspace.init()` doesn't start resolver-backed sandboxes because no sandbox is selected until a request runs. The resolver must return a sandbox that's already started or can start itself on first use.
270
302
 
271
303
  ### Hooks
272
304
 
@@ -285,9 +317,11 @@ const sandbox = new LocalSandbox({
285
317
 
286
318
  ### Cleanup
287
319
 
288
- Sandboxes passed directly to a workspace are owned by that workspace. `mastra dev` and the generated Mastra server handle shutdown signals and destroy registered workspaces automatically. If you embed Mastra in a custom server or process, call `mastra.shutdown()` from its shutdown hook. For a standalone workspace, call `workspace.destroy()` directly. The effect of stopping or destroying the underlying environment depends on the backend.
320
+ When you configure a sandbox directly instead of using a resolver, Mastra owns it. `mastra dev` and the generated Mastra server handle shutdown signals and destroy registered resources automatically. If you embed Mastra in a custom server or process, call `mastra.shutdown()` from its shutdown hook. For standalone use, call `workspace.destroy()` directly.
321
+
322
+ The backend decides what `stop()` and `destroy()` do. For example, stopping may shut down compute while preserving an environment that can restart, while destroying may delete the environment and its ephemeral files. Check the selected backend before relying on either behavior.
289
323
 
290
- Sandboxes returned by a resolver are owned by your application. `workspace.destroy()` and `mastra.shutdown()` clear workspace references but don't destroy those resolved sandboxes. Your resolver or application lifecycle must keep track of them, call `destroy()` when their user, thread, or session ends, and then call `workspace.clearSandboxCache(cacheKey)` for keyed entries. This prevents unused compute from continuing to run and later requests from reusing a stale sandbox.
324
+ Sandboxes returned by a resolver are owned by your application. `workspace.destroy()` and `mastra.shutdown()` clear cached references but don't destroy resolved sandboxes. Application lifecycle code must keep track of them, call `destroy()` when their user, thread, or session ends, and then call `workspace.clearSandboxCache(cacheKey)`. This prevents unused compute from continuing to run and later requests from reusing a stale sandbox.
291
325
 
292
326
  ### Persistence
293
327
 
@@ -335,5 +369,5 @@ await sandbox.executeCommand('node', ['scripts/download-reports.js'], {
335
369
  ## Related
336
370
 
337
371
  - [`SandboxProcessManager` reference](https://mastra.ai/reference/workspace/process-manager)
338
- - [`WorkspaceSandbox` reference](https://mastra.ai/reference/workspace/sandbox)
372
+ - [Sandbox provider interface](https://mastra.ai/reference/workspace/sandbox)
339
373
  - [Filesystem](https://mastra.ai/docs/sandbox/filesystem)