@mastra/mcp-docs-server 1.2.13 → 1.2.14-alpha.3
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/agents/a2a.md +1 -1
- package/.docs/docs/agents/acp.md +1 -1
- package/.docs/docs/agents/agent-approval.md +1 -1
- package/.docs/docs/agents/networks.md +2 -2
- package/.docs/docs/agents/overview.md +2 -2
- package/.docs/docs/agents/using-tools.md +2 -2
- package/.docs/docs/capabilities/channels/overview.md +1 -1
- package/.docs/docs/{agents/supervisor-agents.md → capabilities/subagents.md} +58 -58
- package/.docs/docs/deployment/sandbox.md +95 -1
- package/.docs/docs/getting-started/develop.md +2 -0
- package/.docs/docs/harness/agent-controller.md +370 -0
- package/.docs/docs/long-running-agents/background-tasks.md +1 -1
- package/.docs/docs/long-running-agents/goals.md +3 -3
- package/.docs/docs/mcp/overview.md +222 -281
- package/.docs/docs/memory/overview.md +2 -2
- package/.docs/docs/observability/integrations/exporters/confident-ai.md +140 -0
- package/.docs/docs/observability/integrations/overview.md +1 -1
- package/.docs/docs/observability/overview.md +122 -8
- package/.docs/docs/observability/tracing/overview.md +2 -2
- package/.docs/docs/server/mastra-client.md +1 -1
- package/.docs/docs/server/server-adapters.md +2 -0
- package/.docs/docs/storage/overview.md +2 -2
- package/.docs/guides/build-your-ui/copilotkit/channels.md +49 -39
- package/.docs/guides/concepts/multi-agent-systems.md +7 -7
- package/.docs/guides/guide/coding-agent.md +2 -2
- package/.docs/guides/guide/research-coordinator.md +1 -1
- package/.docs/guides/migrations/network-to-supervisor.md +1 -1
- package/.docs/models/index.md +1 -1
- package/.docs/models/providers/cortecs.md +2 -1
- package/.docs/models/providers/digitalocean.md +9 -9
- package/.docs/models/providers/kilo.md +1 -1
- package/.docs/models/providers/llmgateway.md +1 -1
- package/.docs/models/providers/opencode.md +2 -1
- package/.docs/reference/acp/acp-agent.md +1 -1
- package/.docs/reference/agent-controller/agent-controller-class.md +195 -506
- package/.docs/reference/agent-controller/session.md +274 -111
- package/.docs/reference/agents/network.md +1 -1
- package/.docs/reference/ai-sdk/handle-network-stream.md +1 -1
- package/.docs/reference/ai-sdk/network-route.md +1 -1
- package/.docs/reference/cli/create-mastra.md +1 -1
- package/.docs/reference/editor/versioning.md +1 -1
- package/.docs/reference/evals/rubric.md +1 -1
- package/.docs/reference/file-based-agents/observability.md +2 -2
- package/.docs/reference/file-based-agents/storage.md +1 -1
- package/.docs/reference/file-based-agents/subagents.md +1 -1
- package/.docs/reference/file-based-agents/tools.md +1 -1
- package/.docs/reference/observability/tracing/configuration.md +1 -1
- package/.docs/reference/observability/tracing/exporters/confident-ai.md +138 -0
- package/.docs/reference/observability/tracing/interfaces.md +29 -0
- package/.docs/reference/signals/signal-provider.md +2 -0
- package/.docs/reference/tools/mcp-server.md +1 -1
- package/.docs/reference/tools/submit-plan-tool.md +1 -1
- package/.docs/reference/workspace/railway-sandbox.md +5 -5
- package/CHANGELOG.md +14 -0
- package/package.json +3 -3
- package/.docs/docs/agent-controller/channels.md +0 -111
- package/.docs/docs/agent-controller/modes.md +0 -147
- package/.docs/docs/agent-controller/overview.md +0 -136
- package/.docs/docs/agent-controller/session.md +0 -161
- package/.docs/docs/agent-controller/subagents.md +0 -110
- package/.docs/docs/agent-controller/threads-and-state.md +0 -148
- package/.docs/docs/agent-controller/tool-approvals.md +0 -147
- package/.docs/docs/mcp/mcp-apps.md +0 -306
- package/.docs/docs/observability/config.md +0 -140
- package/.docs/docs/observability/storage.md +0 -81
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
> Discover all available pages from the documentation index: https://mastra.ai/llms.txt
|
|
2
|
-
|
|
3
|
-
# Threads and state
|
|
4
|
-
|
|
5
|
-
Threads and state are how an AgentController conversation survives beyond a single exchange. A **thread** is the persistent record of a conversation, its messages and metadata, saved to storage so a user can close the app and resume the same conversation later, or switch between several conversations. **State** is structured data attached to the conversation, values like model preferences, feature flags, or progress, that agents and your UI read and write as the conversation runs.
|
|
6
|
-
|
|
7
|
-
The thread and state work together: the thread is the message history, and state is the shared scratchpad alongside it.
|
|
8
|
-
|
|
9
|
-
Both persist across mode switches, model changes, and restarts, so nothing is lost when a user switches from plan mode to build mode or reopens the app the next day.
|
|
10
|
-
|
|
11
|
-
Thread _lifecycle_ transitions, create, switch, clone, delete, live on the AgentController because they coordinate the shared thread lock and emit events. The active thread binding and thread/message _reads_ live on the [`Session`](https://mastra.ai/docs/agent-controller/session) as `agentController.session.thread`.
|
|
12
|
-
|
|
13
|
-
## Threads
|
|
14
|
-
|
|
15
|
-
A thread holds one conversation's full message history. The AgentController binds the Session to one active thread at a time. Messages you send and the agent's replies are appended to that thread and saved to storage. Threads let users resume a past conversation or keep several conversations side by side, or alternatively branch one into alternatives.
|
|
16
|
-
|
|
17
|
-
### Creating and selecting threads
|
|
18
|
-
|
|
19
|
-
On startup, call `selectOrCreateThread()` to resume the most recent thread or create a new one:
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
await agentController.init()
|
|
23
|
-
const thread = await agentController.selectOrCreateThread()
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Create a thread explicitly with a title:
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
const thread = await agentController.createThread({ title: 'New conversation' })
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Switching threads
|
|
33
|
-
|
|
34
|
-
Switch to an existing thread.
|
|
35
|
-
|
|
36
|
-
The agentController aborts any in-progress generation and acquires a lock on the new thread, plus emits a `thread_changed` event:
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
await agentController.switchThread({ threadId: 'thread-abc123' })
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### Listing threads
|
|
43
|
-
|
|
44
|
-
List threads for the current resource. Forked subagent threads are hidden by default:
|
|
45
|
-
|
|
46
|
-
```typescript
|
|
47
|
-
const threads = await agentController.session.thread.list()
|
|
48
|
-
|
|
49
|
-
// Include all resources
|
|
50
|
-
const allThreads = await agentController.session.thread.list({ allResources: true })
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### Cloning threads
|
|
54
|
-
|
|
55
|
-
Clone a thread to create a branch of the conversation. The agentController copies all messages and switches to the clone:
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
const cloned = await agentController.cloneThread({ title: 'Alternative approach' })
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Thread locking
|
|
62
|
-
|
|
63
|
-
Pass a `threadLock` to the AgentController constructor to prevent concurrent access from multiple processes. The lock is acquired before any thread operation and released on switch or delete:
|
|
64
|
-
|
|
65
|
-
```typescript
|
|
66
|
-
const agentController = new AgentController({
|
|
67
|
-
id: 'my-agent',
|
|
68
|
-
threadLock: {
|
|
69
|
-
acquire: async threadId => {
|
|
70
|
-
/* acquire lock or throw */
|
|
71
|
-
},
|
|
72
|
-
release: async threadId => {
|
|
73
|
-
/* release lock */
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
})
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## State
|
|
80
|
-
|
|
81
|
-
Where a thread stores the conversation's messages, state stores structured values that describe the conversation but aren't messages, model preferences, feature flags, UI settings, or progress markers. Agents can read and update state during a run, and your UI can react to changes, so state is how the agent and the interface stay in sync on shared facts. You define its shape with a schema, and every update is validated against that schema before it's applied.
|
|
82
|
-
|
|
83
|
-
### Defining a state schema
|
|
84
|
-
|
|
85
|
-
Pass a `stateSchema` (Standard JSON Schema) to validate state and extract defaults:
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
import { Agent } from '@mastra/core/agent'
|
|
89
|
-
import { AgentController } from '@mastra/core/agent-controller'
|
|
90
|
-
import { z } from 'zod'
|
|
91
|
-
|
|
92
|
-
const agent = new Agent({
|
|
93
|
-
id: 'assistant',
|
|
94
|
-
name: 'assistant',
|
|
95
|
-
instructions: 'Help the user manage a stateful session.',
|
|
96
|
-
model: 'openai/gpt-5.6-sol',
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
const agentController = new AgentController({
|
|
100
|
-
id: 'stateful-agent',
|
|
101
|
-
agent,
|
|
102
|
-
modes: [{ id: 'default', name: 'Default', metadata: { default: true } }],
|
|
103
|
-
stateSchema: z.object({
|
|
104
|
-
currentModelId: z.string().optional(),
|
|
105
|
-
theme: z.enum(['light', 'dark']).default('dark'),
|
|
106
|
-
}),
|
|
107
|
-
})
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### Reading and writing state
|
|
111
|
-
|
|
112
|
-
State is owned by the [`Session`](https://mastra.ai/docs/agent-controller/session) as `agentController.session.state`:
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
// Read the current state snapshot
|
|
116
|
-
const state = agentController.session.state.get()
|
|
117
|
-
|
|
118
|
-
// Update state — validates against schema and emits state_changed
|
|
119
|
-
await agentController.session.state.set({ theme: 'light' })
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
State changes emit a `state_changed` event with the new state and the set of changed keys.
|
|
123
|
-
|
|
124
|
-
## Resource IDs
|
|
125
|
-
|
|
126
|
-
Threads are scoped to a resource ID, which groups a conversation's threads by project, user, or workspace. Set it on the AgentController constructor; it defaults to the agentController `id` when omitted:
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
const agentController = new AgentController({
|
|
130
|
-
id: 'my-agent',
|
|
131
|
-
resourceId: 'project-xyz',
|
|
132
|
-
})
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
The resource ID is part of a conversation's identity, so you read it from the Session:
|
|
136
|
-
|
|
137
|
-
```typescript
|
|
138
|
-
const resourceId = agentController.session.identity.getResourceId()
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
The session also has a stable `id` and `ownerId` (read with `session.identity.getId()` and `session.identity.getOwnerId()`). Unlike the resource ID, these don't change when you switch resources, see [Session identity](https://mastra.ai/docs/agent-controller/session) for details.
|
|
142
|
-
|
|
143
|
-
## Related
|
|
144
|
-
|
|
145
|
-
- [AgentController overview](https://mastra.ai/docs/agent-controller/overview)
|
|
146
|
-
- [Session](https://mastra.ai/docs/agent-controller/session)
|
|
147
|
-
- [Modes](https://mastra.ai/docs/agent-controller/modes)
|
|
148
|
-
- [API reference](https://mastra.ai/reference/agent-controller/agent-controller-class)
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
> Discover all available pages from the documentation index: https://mastra.ai/llms.txt
|
|
2
|
-
|
|
3
|
-
# Tool approvals and permissions
|
|
4
|
-
|
|
5
|
-
The AgentController provides a permission system that controls which tools require user approval before execution.
|
|
6
|
-
|
|
7
|
-
You can configure policies at the category level or per-tool, and grant session-wide exceptions for trusted tools.
|
|
8
|
-
|
|
9
|
-
This gives agents with access to destructive or sensitive tools, file writes, command execution, API calls, a human-in-the-loop checkpoint before those tools run.
|
|
10
|
-
|
|
11
|
-
## Permission policies
|
|
12
|
-
|
|
13
|
-
Policies control tool behavior:
|
|
14
|
-
|
|
15
|
-
- `allow`: The tool runs without prompting
|
|
16
|
-
- `ask`: The tool pauses and emits a `tool_approval_required` event; the user must approve or decline
|
|
17
|
-
- `deny`: The tool is blocked from execution
|
|
18
|
-
|
|
19
|
-
### Setting policies
|
|
20
|
-
|
|
21
|
-
Set policies per-category or per-tool. Per-tool policies take precedence over category policies:
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
// Category-level: all execute tools require approval
|
|
25
|
-
await agentController.session.permissions.setForCategory({ category: 'execute', policy: 'ask' })
|
|
26
|
-
|
|
27
|
-
// Tool-level: this specific tool is always blocked
|
|
28
|
-
await agentController.session.permissions.setForTool({ toolName: 'dangerous_tool', policy: 'deny' })
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Tool categories
|
|
32
|
-
|
|
33
|
-
The `toolCategoryResolver` maps tool names to categories. Pass it to the AgentController constructor:
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
const agentController = new AgentController({
|
|
37
|
-
id: 'my-agent',
|
|
38
|
-
toolCategoryResolver: toolName => {
|
|
39
|
-
if (toolName.includes('write') || toolName.includes('delete')) return 'edit'
|
|
40
|
-
if (toolName.includes('execute')) return 'execute'
|
|
41
|
-
return 'read'
|
|
42
|
-
},
|
|
43
|
-
})
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Built-in categories are `read`, `edit`, `execute`, `mcp`, and `other`.
|
|
47
|
-
|
|
48
|
-
## Responding to approval requests
|
|
49
|
-
|
|
50
|
-
When a tool's policy is `ask`, the AgentController emits a `tool_approval_required` event. Your UI should display a prompt and call `session.respondToToolApproval()`:
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
agentController.subscribe(event => {
|
|
54
|
-
if (event.type === 'tool_approval_required') {
|
|
55
|
-
// Show approval UI...
|
|
56
|
-
agentController.session.respondToToolApproval({ decision: 'approve' })
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
The `decision` field accepts `'approve'`, `'decline'`, or `'always_allow_category'`. When `always_allow_category` is used, the tool's category is granted for the rest of the session. Future tools in the same category are auto-approved.
|
|
62
|
-
|
|
63
|
-
```typescript
|
|
64
|
-
agentController.session.respondToToolApproval({ decision: 'always_allow_category' })
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## Session grants
|
|
68
|
-
|
|
69
|
-
The AgentController owns permission _policy_ (which categories require approval). The [`Session`](https://mastra.ai/docs/agent-controller/session) owns the _grants_ a user makes during a conversation. Grant a category or tool for the rest of the session so it runs without further prompting:
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
// Grant all edit tools for this session
|
|
73
|
-
agentController.session.grantCategory('edit')
|
|
74
|
-
|
|
75
|
-
// Grant a specific tool
|
|
76
|
-
agentController.session.grantTool('mastra_workspace_execute_command')
|
|
77
|
-
|
|
78
|
-
// Check current grants
|
|
79
|
-
const grants = agentController.session.getGrants()
|
|
80
|
-
// { categories: ['edit'], tools: ['mastra_workspace_execute_command'] }
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## Tool suspensions
|
|
84
|
-
|
|
85
|
-
Interactive built-in tools (`ask_user`, `submit_plan`) use the native tool-suspension primitive instead of the approval flow. They emit a `tool_suspended` event with `toolCallId`, `toolName`, and `suspendPayload`. Resume with `respondToToolSuspension()`:
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
agentController.subscribe(event => {
|
|
89
|
-
if (event.type === 'tool_suspended' && event.toolName === 'ask_user') {
|
|
90
|
-
const { question } = event.suspendPayload as { question: string }
|
|
91
|
-
// Show question to user, then resume:
|
|
92
|
-
agentController.respondToToolSuspension({
|
|
93
|
-
toolCallId: event.toolCallId,
|
|
94
|
-
resumeData: 'User response here',
|
|
95
|
-
})
|
|
96
|
-
}
|
|
97
|
-
})
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### Plan approval
|
|
101
|
-
|
|
102
|
-
The `submit_plan` tool suspends via the same mechanism. Resume with an `action` field:
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
// Approve the plan
|
|
106
|
-
agentController.respondToToolSuspension({
|
|
107
|
-
toolCallId: event.toolCallId,
|
|
108
|
-
resumeData: { action: 'approved' },
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
// Reject with feedback
|
|
112
|
-
agentController.respondToToolSuspension({
|
|
113
|
-
toolCallId: event.toolCallId,
|
|
114
|
-
resumeData: { action: 'rejected', feedback: 'Needs more detail' },
|
|
115
|
-
})
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Built-in tools
|
|
119
|
-
|
|
120
|
-
The AgentController provides these built-in tools to agents in every mode:
|
|
121
|
-
|
|
122
|
-
| Tool | Description |
|
|
123
|
-
| --------------- | ------------------------------------------------------------------- |
|
|
124
|
-
| `ask_user` | Ask the user a question (free text, single-select, or multi-select) |
|
|
125
|
-
| `submit_plan` | Submit a plan for user review and approval |
|
|
126
|
-
| `task_write` | Create or replace a structured task list |
|
|
127
|
-
| `task_update` | Update one tracked task by ID |
|
|
128
|
-
| `task_complete` | Mark one tracked task completed |
|
|
129
|
-
| `task_check` | Check task list completion status |
|
|
130
|
-
| `subagent` | Spawn a focused subagent (requires `subagents` config) |
|
|
131
|
-
|
|
132
|
-
Disable specific built-in tools with `disableBuiltinTools`:
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
const agentController = new AgentController({
|
|
136
|
-
id: 'no-plans',
|
|
137
|
-
disableBuiltinTools: ['submit_plan'],
|
|
138
|
-
})
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Related
|
|
142
|
-
|
|
143
|
-
- [AgentController overview](https://mastra.ai/docs/agent-controller/overview)
|
|
144
|
-
- [Session](https://mastra.ai/docs/agent-controller/session)
|
|
145
|
-
- [Subagents](https://mastra.ai/docs/agent-controller/subagents)
|
|
146
|
-
- [Agent approval](https://mastra.ai/docs/agents/agent-approval)
|
|
147
|
-
- [API reference](https://mastra.ai/reference/agent-controller/agent-controller-class)
|
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
> Discover all available pages from the documentation index: https://mastra.ai/llms.txt
|
|
2
|
-
|
|
3
|
-
# MCP Apps
|
|
4
|
-
|
|
5
|
-
The [MCP Apps extension](https://github.com/modelcontextprotocol/ext-apps) allows MCP tools to serve interactive HTML UIs via `ui://` resources. When a tool has an associated app resource, Mastra Studio renders it in a sandboxed iframe alongside the tool form or inline in agent chat.
|
|
6
|
-
|
|
7
|
-
## When to use MCP Apps
|
|
8
|
-
|
|
9
|
-
Use MCP Apps when a tool result is better presented as an interactive UI rather than plain text. For example:
|
|
10
|
-
|
|
11
|
-
- A calculator that renders input fields and buttons for computation
|
|
12
|
-
- A color picker that displays swatches and hex values
|
|
13
|
-
- A form builder that captures structured user input
|
|
14
|
-
- A data visualizer that renders charts
|
|
15
|
-
|
|
16
|
-
## Quickstart
|
|
17
|
-
|
|
18
|
-
Define app resources on your `MCPServer` by providing a `ui://` URI mapped to inline HTML or an HTML file path.
|
|
19
|
-
|
|
20
|
-
```typescript
|
|
21
|
-
import { MCPServer } from '@mastra/mcp'
|
|
22
|
-
import { createTool } from '@mastra/core/tools'
|
|
23
|
-
import { z } from 'zod'
|
|
24
|
-
|
|
25
|
-
const calculatorTool = createTool({
|
|
26
|
-
id: 'calculatorWithUI',
|
|
27
|
-
description: 'An interactive calculator',
|
|
28
|
-
inputSchema: z.object({
|
|
29
|
-
num1: z.number(),
|
|
30
|
-
num2: z.number(),
|
|
31
|
-
operation: z.enum(['add', 'subtract']),
|
|
32
|
-
}),
|
|
33
|
-
execute: async ({ num1, num2, operation }) => {
|
|
34
|
-
const result = operation === 'add' ? num1 + num2 : num1 - num2
|
|
35
|
-
return {
|
|
36
|
-
content: [{ type: 'text', text: 'An interactive calculator is displayed.' }],
|
|
37
|
-
structuredContent: { result },
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
const server = new MCPServer({
|
|
43
|
-
id: 'my-app-server',
|
|
44
|
-
name: 'My App Server',
|
|
45
|
-
version: '1.0.0',
|
|
46
|
-
tools: { calculatorTool },
|
|
47
|
-
appResources: {
|
|
48
|
-
'ui://calculator/main': {
|
|
49
|
-
name: 'Interactive Calculator',
|
|
50
|
-
html: `<html>
|
|
51
|
-
<body>
|
|
52
|
-
<h2>Calculator</h2>
|
|
53
|
-
<button id="btn">Compute</button>
|
|
54
|
-
<script type="module">
|
|
55
|
-
import { App } from 'https://cdn.jsdelivr.net/npm/@modelcontextprotocol/ext-apps/+esm';
|
|
56
|
-
const app = new App({ name: 'Calculator', version: '1.0.0' });
|
|
57
|
-
app.ontoolinput = (params) => {
|
|
58
|
-
console.log('Tool input:', params.arguments);
|
|
59
|
-
};
|
|
60
|
-
document.getElementById('btn').addEventListener('click', async () => {
|
|
61
|
-
const result = await app.callServerTool({
|
|
62
|
-
name: 'calculatorWithUI',
|
|
63
|
-
arguments: { num1: 10, num2: 5, operation: 'add' }
|
|
64
|
-
});
|
|
65
|
-
document.body.innerHTML += '<p>Result: ' + JSON.stringify(result) + '</p>';
|
|
66
|
-
});
|
|
67
|
-
await app.connect();
|
|
68
|
-
</script>
|
|
69
|
-
</body>
|
|
70
|
-
</html>`,
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
})
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Link the tool to its app resource by adding `_meta.ui.resourceUri` to the tool definition:
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
calculatorTool._meta = {
|
|
80
|
-
ui: { resourceUri: 'ui://calculator/main' },
|
|
81
|
-
}
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Visit [MCPServer reference](https://mastra.ai/reference/tools/mcp-server) for the full `appResources` configuration.
|
|
85
|
-
|
|
86
|
-
## Connecting MCP Apps to agents
|
|
87
|
-
|
|
88
|
-
Agents consume tools, they don't need to know about MCP servers. Pass tools to the agent's `tools` config, and register the MCP server at the Mastra level so Studio can resolve app resources.
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
import { Agent } from '@mastra/core/agent'
|
|
92
|
-
import { calculatorTool } from '../mcp/tools'
|
|
93
|
-
|
|
94
|
-
export const myAgent = new Agent({
|
|
95
|
-
id: 'my-agent',
|
|
96
|
-
name: 'My Agent',
|
|
97
|
-
instructions: 'You have access to interactive UI tools.',
|
|
98
|
-
model: 'openai/gpt-5-mini',
|
|
99
|
-
tools: { calculatorTool },
|
|
100
|
-
})
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Register the MCP server at the Mastra level. Studio scans registered MCP servers to map tools to their app resources.
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
import { Mastra } from '@mastra/core/mastra'
|
|
107
|
-
import { myAgent } from './agents'
|
|
108
|
-
import { myAppServer } from './mcp/server'
|
|
109
|
-
|
|
110
|
-
export const mastra = new Mastra({
|
|
111
|
-
agents: { myAgent },
|
|
112
|
-
mcpServers: { myAppServer },
|
|
113
|
-
})
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
For remote MCP servers, use `MCPClient.listTools()` to get tools and `toMCPServerProxies()` to register the server:
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
import { MCPClient } from '@mastra/mcp'
|
|
120
|
-
|
|
121
|
-
const mcpClient = new MCPClient({
|
|
122
|
-
servers: {
|
|
123
|
-
remoteApp: { url: new URL('https://remote-mcp-server.example.com/mcp') },
|
|
124
|
-
},
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
const myAgent = new Agent({
|
|
128
|
-
id: 'my-agent',
|
|
129
|
-
name: 'My Agent',
|
|
130
|
-
model: 'openai/gpt-5-mini',
|
|
131
|
-
tools: await mcpClient.listTools(),
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
export const mastra = new Mastra({
|
|
135
|
-
agents: { myAgent },
|
|
136
|
-
mcpServers: { ...mcpClient.toMCPServerProxies() },
|
|
137
|
-
})
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
When tools come from `MCPClient.listTools()`, each tool's `_meta.ui` is automatically stamped with a `serverId` so Studio can resolve its app resources without scanning all servers.
|
|
141
|
-
|
|
142
|
-
## How MCP Apps work
|
|
143
|
-
|
|
144
|
-
MCP Apps follow a specific communication pattern between the host (Mastra Studio) and the iframe:
|
|
145
|
-
|
|
146
|
-
1. The tool executes and returns a brief summary in `content` (visible to the model) and detailed data in `structuredContent` (visible to the UI only).
|
|
147
|
-
2. The host renders the app HTML in a sandboxed iframe.
|
|
148
|
-
3. The iframe communicates with the host via a JSON-RPC postMessage protocol.
|
|
149
|
-
4. The app can call server tools using `callServerTool()` and inject messages into the chat using `sendMessage()`.
|
|
150
|
-
|
|
151
|
-
```text
|
|
152
|
-
Agent calls tool → Tool returns brief content + structuredContent
|
|
153
|
-
→ Host renders iframe with app HTML
|
|
154
|
-
→ User interacts with UI
|
|
155
|
-
→ UI calls callServerTool() for computation
|
|
156
|
-
→ UI calls sendMessage() to inject result into chat
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
## Tool result format
|
|
160
|
-
|
|
161
|
-
Tools with app resources should return two fields:
|
|
162
|
-
|
|
163
|
-
- `content`: A brief text summary for the model. Keep this short so the agent doesn't parrot the full result.
|
|
164
|
-
- `structuredContent`: The data payload that hydrates the UI. The model doesn't see this field.
|
|
165
|
-
|
|
166
|
-
```typescript
|
|
167
|
-
execute: async ({ num1, num2, operation }) => {
|
|
168
|
-
const result = operation === 'add' ? num1 + num2 : num1 - num2
|
|
169
|
-
return {
|
|
170
|
-
content: [{ type: 'text', text: 'An interactive calculator is displayed.' }],
|
|
171
|
-
structuredContent: { result },
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## App API (guest-side)
|
|
177
|
-
|
|
178
|
-
MCP App HTML uses the standard [`App` class from `@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps) to communicate with the host. Import it via ESM CDN or bundle it.
|
|
179
|
-
|
|
180
|
-
```javascript
|
|
181
|
-
import { App } from 'https://cdn.jsdelivr.net/npm/@modelcontextprotocol/ext-apps/+esm'
|
|
182
|
-
const app = new App({ name: 'MyApp', version: '1.0.0' })
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### `app.callServerTool(params)`
|
|
186
|
-
|
|
187
|
-
Calls an MCP server tool from within the iframe. This is useful for interactive computation without leaving the UI.
|
|
188
|
-
|
|
189
|
-
```javascript
|
|
190
|
-
const result = await app.callServerTool({
|
|
191
|
-
name: 'calculatorWithUI',
|
|
192
|
-
arguments: { num1: 42, num2: 8, operation: 'add' },
|
|
193
|
-
})
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
### `app.sendMessage(params)`
|
|
197
|
-
|
|
198
|
-
Injects a user message into the agent chat, triggering a new model turn. Use this for sharing results or requesting follow-up actions.
|
|
199
|
-
|
|
200
|
-
```javascript
|
|
201
|
-
await app.sendMessage({
|
|
202
|
-
role: 'user',
|
|
203
|
-
content: [{ type: 'text', text: 'The result of 42 + 8 is 50' }],
|
|
204
|
-
})
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### `app.ontoolinput`
|
|
208
|
-
|
|
209
|
-
A callback that fires when the host delivers tool input data to the iframe, allowing pre-population of form fields. The `params.arguments` object contains the tool call arguments.
|
|
210
|
-
|
|
211
|
-
```javascript
|
|
212
|
-
app.ontoolinput = params => {
|
|
213
|
-
document.getElementById('num1').value = params.arguments.num1
|
|
214
|
-
}
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
> **Preventing UI flicker:** If your app has default form values, the user may briefly see them before `ontoolinput` hydrates the correct values. To prevent this, start the body hidden and reveal it after hydration:
|
|
218
|
-
>
|
|
219
|
-
> ```html
|
|
220
|
-
> <style>
|
|
221
|
-
> body {
|
|
222
|
-
> opacity: 0;
|
|
223
|
-
> transition: opacity 0.15s;
|
|
224
|
-
> }
|
|
225
|
-
> body.ready {
|
|
226
|
-
> opacity: 1;
|
|
227
|
-
> }
|
|
228
|
-
> </style>
|
|
229
|
-
> <script type="module">
|
|
230
|
-
> import { App } from 'https://cdn.jsdelivr.net/npm/@modelcontextprotocol/ext-apps/+esm'
|
|
231
|
-
> const app = new App({ name: 'MyApp', version: '1.0.0' })
|
|
232
|
-
>
|
|
233
|
-
> app.ontoolinput = params => {
|
|
234
|
-
> // Hydrate form fields from params.arguments
|
|
235
|
-
> document.body.classList.add('ready')
|
|
236
|
-
> }
|
|
237
|
-
>
|
|
238
|
-
> await app.connect()
|
|
239
|
-
> // Fallback: reveal after connection if no tool input arrives
|
|
240
|
-
> setTimeout(() => document.body.classList.add('ready'), 150)
|
|
241
|
-
> </script>
|
|
242
|
-
> ```
|
|
243
|
-
|
|
244
|
-
### `app.connect()`
|
|
245
|
-
|
|
246
|
-
Establishes the connection to the host. Call this after registering all event handlers.
|
|
247
|
-
|
|
248
|
-
```javascript
|
|
249
|
-
await app.connect()
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
See the [`App` class API reference](https://apps.extensions.modelcontextprotocol.io/api/classes/app.App.html) for the full list of methods, callbacks, and lifecycle hooks.
|
|
253
|
-
|
|
254
|
-
## Using external MCP servers with apps
|
|
255
|
-
|
|
256
|
-
External (non-Mastra) MCP servers that implement the MCP Apps extension work with Mastra via `MCPClient`. Use `listTools()` for agent tools and `toMCPServerProxies()` to register them in Studio.
|
|
257
|
-
|
|
258
|
-
```typescript
|
|
259
|
-
import { Mastra } from '@mastra/core/mastra'
|
|
260
|
-
import { MCPClient } from '@mastra/mcp'
|
|
261
|
-
import { Agent } from '@mastra/core/agent'
|
|
262
|
-
|
|
263
|
-
const mcpClient = new MCPClient({
|
|
264
|
-
servers: {
|
|
265
|
-
'external-server': {
|
|
266
|
-
command: 'node',
|
|
267
|
-
args: ['path/to/external-server.js'],
|
|
268
|
-
},
|
|
269
|
-
},
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
const myAgent = new Agent({
|
|
273
|
-
id: 'my-agent',
|
|
274
|
-
name: 'My Agent',
|
|
275
|
-
model: 'openai/gpt-5-mini',
|
|
276
|
-
tools: await mcpClient.listTools(),
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
export const mastra = new Mastra({
|
|
280
|
-
agents: { myAgent },
|
|
281
|
-
mcpServers: {
|
|
282
|
-
...mcpClient.toMCPServerProxies(),
|
|
283
|
-
},
|
|
284
|
-
})
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
Visit [MCPClient reference](https://mastra.ai/reference/tools/mcp-client) for more details on proxying external servers.
|
|
288
|
-
|
|
289
|
-
## Sandbox security
|
|
290
|
-
|
|
291
|
-
Mastra Studio uses [`@mcp-ui/client`](https://www.npmjs.com/package/@mcp-ui/client) to render MCP App iframes through a sandbox proxy. The proxy loads app HTML via `postMessage` rather than `srcDoc`, providing additional isolation.
|
|
292
|
-
|
|
293
|
-
App iframes are sandboxed with the following permissions:
|
|
294
|
-
|
|
295
|
-
- `allow-scripts`: Enables JavaScript execution
|
|
296
|
-
- `allow-forms`: Allows form submission
|
|
297
|
-
- `allow-popups`: Permits `window.open()` and link targets
|
|
298
|
-
|
|
299
|
-
The iframe doesn't have access to the parent page's DOM, cookies, or storage. All communication happens through the JSON-RPC postMessage protocol managed by `@mcp-ui/client`'s `AppRenderer` on the host side and `@modelcontextprotocol/ext-apps`'s `App` class on the guest side.
|
|
300
|
-
|
|
301
|
-
## Related
|
|
302
|
-
|
|
303
|
-
- [MCP overview](https://mastra.ai/docs/mcp/overview)
|
|
304
|
-
- [MCPServer reference](https://mastra.ai/reference/tools/mcp-server)
|
|
305
|
-
- [MCPClient reference](https://mastra.ai/reference/tools/mcp-client)
|
|
306
|
-
- [MCP Apps extension spec](https://github.com/modelcontextprotocol/ext-apps)
|