Skip to content

Commit 6c86bda

Browse files
TylerBarnesMastra Code (openai/gpt-5.5)
andauthored
feat: add Mastra Code plugin system (#18658)
Adds the first Mastra Code plugin system so users can install trusted local or GitHub plugins and expose their tools inside the TUI. Plugins can be a local path, or installed via a github url. When installing from github it will clone it to the installed scope (project or global) and MC will then periodically check if it needs to pull new commits down. For local plugins, it hot reloads tool changes, allowing MC to work on its own tools via a plugin. It can modify the tool, call it, modify, etc in a loop until it works properly. ```ts import { defineMastraCodePlugin, createTool, z } from 'mastracode/plugin'; export default defineMastraCodePlugin({ id: 'example.plugin', name: 'Example Plugin', tools: { example_tool: { tool: createTool({ id: 'example_tool', description: 'Run an example plugin tool', inputSchema: z.object({ message: z.string() }), execute: async context => ({ message: context.message }), }), }, }, }); ``` Plugins can define tools, optional config, render hints, bundled slash commands and skills, and plugin instructions. The `/plugins` UI handles install, scaffold, details, config, enable/disable, and local/GitHub source management. Local plugin edits reload at execution time, and GitHub checkouts poll for updates while preserving local changes on backup branches before resetting. This also adds project-level plugin blocking, progress streaming for plugin tools, subagent-style rendering for plugin tools that ask for it, and a README note that plugins should only be installed from trusted sources. Smoke tested with Alexandria: the expert tool executes, config persists, and bundled command/skill loading was tested earlier. Focused unit tests, typecheck, and `pnpm build:mastracode` pass. example plugin: https://github.com/mastra-ai/alexandria/blob/main/.mastracode/plugins/sources/local/alexandria/src/index.ts#L101 <img width="675" height="103" alt="Screenshot 2026-06-29 at 5 07 24 PM" src="https://github.com/user-attachments/assets/74db5628-e546-421b-9916-f68a9eed281d" /> <img width="890" height="328" alt="Screenshot 2026-06-29 at 5 07 39 PM" src="https://github.com/user-attachments/assets/9b3c6add-1d98-4583-8e74-0a0f9fdbe99d" /> <img width="721" height="336" alt="Screenshot 2026-06-29 at 5 07 34 PM" src="https://github.com/user-attachments/assets/f2d3e0be-c9b8-41ed-9208-56ff82f380ed" /> <img width="564" height="506" alt="Screenshot 2026-06-29 at 5 07 44 PM" src="https://github.com/user-attachments/assets/6f082267-52c5-43a0-98af-3b3e06d4f582" /> <img width="2530" height="1156" alt="image (34)" src="https://github.com/user-attachments/assets/e964bf8c-dbd3-4ead-b25e-3ea848ac93bf" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## ELI5 This PR adds a way for Mastra Code to load “trusted plugin” code so it can learn new tools and commands. You can install plugins from your computer or GitHub, manage them in the terminal UI, and see their tool progress update live—plus reload plugins automatically when local code changes or GitHub has updates. ## Summary - Introduces the first Mastra Code plugin system: - Public plugin API/types (`defineMastraCodePlugin`) and `writeToolProgress` support - `.mastracode-plugin.json` manifest handling - Local + GitHub install, local discovery, scoped plugin registries (load/merge/save), and a full plugin loader pipeline - A `PluginManager` that tracks active plugins, supports reloads, and handles local hot-reload + GitHub polling/update with backup/dirtiness safety - Project-level plugin blocking via `disabledPlugins` - Wires plugins into the app runtime so plugin contributions work everywhere: - Merges plugin tool names into mode allowlists and dynamically builds toolsets from loaded plugin tools - Appends plugin-provided instructions into generated agent prompts - Exposes plugin-provided assets via TUI/session state (skills/commands/instructions), and treats plugin command dirs as extra high-priority slash-command sources - Adds `/plugins` TUI management with end-to-end flows: - Install new plugins, scaffold plugin projects, view details, configure plugin config values (including model selection + API key prompting), enable/disable, uninstall - Install-source management (local path vs GitHub URL) with trust-confirmation guidance - Includes block/disabled handling in the UI and behavior (hidden/conflicted/blocked states) - Improves plugin tool UX in chat/TUI: - Streams plugin tool progress from core → TUI using `data-mastracode-tool-progress`, emitting tool updates and rendering progress output - Adds “subagent-style” rendering for tools that request it, including static subagent component handling and replay for previously stored tool calls - Supports local hot reloading and GitHub polling/update scenarios - Tests + release notes: - Adds unit tests for plugin loader/manager/registry/scaffold, plugin instruction generation, tool-progress streaming, and tool rendering precedence - Adds TUI unit tests for `/plugins` - Adds E2E fixtures covering bundled commands/skills, tool streaming, local hot reload, GitHub poll updates, blocking/config behavior, and scaffold/install/execute flows - Updates docs and adds a changeset publishing “Mastra Code” plugin support. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Mastra Code (openai/gpt-5.5) <noreply@mastra.ai>
1 parent 6f578ac commit 6c86bda

66 files changed

Lines changed: 5440 additions & 69 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/true-clowns-divide.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
'mastracode': patch
3+
---
4+
5+
Added Mastra Code plugin support:
6+
7+
- Install, scaffold, configure, block, and auto-update plugins with local-change backups.
8+
- Load plugin tools in all modes, including streaming progress and subagent-style rendering.
9+
- Load bundled plugin commands, skills, and plugin-provided system instructions.
10+
11+
Example:
12+
13+
```ts
14+
import { createTool, defineMastraCodePlugin, z } from 'mastracode/plugin';
15+
16+
export default defineMastraCodePlugin({
17+
id: 'acme.tools',
18+
tools: {
19+
echo: {
20+
tool: createTool({
21+
id: 'echo',
22+
inputSchema: z.object({ message: z.string() }),
23+
execute: async ({ message }) => ({ message }),
24+
}),
25+
},
26+
},
27+
});
28+
```

mastracode/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ Select a suggestion with arrow keys and press Tab to insert it.
101101
| `/mcp` | Show/reload MCP server connections |
102102
| `/sandbox` | Manage allowed paths (add/remove dirs) |
103103
| `/permissions` | View/manage tool approval permissions |
104+
| `/plugins` | Install and manage trusted Mastra Code plugins |
104105
| `/settings` | General settings (notifications, YOLO, etc.) |
105106
| `/yolo` | Toggle YOLO mode (auto-approve all tools) |
106107
| `/resource` | Show/switch resource ID (tag for sharing) |
@@ -111,6 +112,10 @@ Select a suggestion with arrow keys and press Tab to insert it.
111112
| `/help` | Show available commands |
112113
| `/exit` | Exit the TUI |
113114

115+
### Plugins
116+
117+
Use `/plugins` to install and manage trusted local or GitHub plugins. Plugins can add tools, commands, skills, and system instructions. Because plugins execute code inside Mastra Code and their instructions are appended to the agent prompt, only install plugins from sources you trust.
118+
114119
### Goals
115120

116121
Use `/goal <objective>` to have Mastra Code keep working toward an objective across turns. Goals use a judge model to decide whether the goal is complete, should continue, or should wait for an explicit user checkpoint. Configure defaults with `/judge`.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"fixtures": [
3+
{
4+
"match": {
5+
"userMessage": "E2E plugin bundled command executed.\n\nARGUMENTS:",
6+
"model": "gpt-5.4-mini",
7+
"endpoint": "chat"
8+
},
9+
"response": {
10+
"content": "MC plugin bundled command response"
11+
}
12+
},
13+
{
14+
"match": {
15+
"userMessage": "E2E plugin bundled skill instructions.",
16+
"model": "gpt-5.4-mini",
17+
"endpoint": "chat"
18+
},
19+
"response": {
20+
"content": "MC plugin bundled skill response"
21+
}
22+
}
23+
]
24+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{
2+
"fixtures": [
3+
{
4+
"match": {
5+
"userMessage": "Call the GitHub plugin before update.",
6+
"model": "gpt-5.4-mini",
7+
"endpoint": "chat",
8+
"hasToolResult": false
9+
},
10+
"response": {
11+
"toolCalls": [
12+
{
13+
"id": "call_github_poll_before",
14+
"name": "e2e_plugin_lookup",
15+
"arguments": {
16+
"query": "before"
17+
}
18+
}
19+
]
20+
}
21+
},
22+
{
23+
"match": {
24+
"hasToolResult": true,
25+
"toolCallId": "call_github_poll_before",
26+
"model": "gpt-5.4-mini",
27+
"endpoint": "chat"
28+
},
29+
"response": {
30+
"content": "GitHub poll plugin follow-up complete."
31+
}
32+
},
33+
{
34+
"match": {
35+
"model": "gpt-5.4-mini",
36+
"endpoint": "chat"
37+
},
38+
"response": {
39+
"toolCalls": [
40+
{
41+
"id": "call_github_poll_after",
42+
"name": "e2e_plugin_lookup",
43+
"arguments": {
44+
"query": "after"
45+
}
46+
}
47+
]
48+
}
49+
},
50+
{
51+
"match": {
52+
"hasToolResult": true,
53+
"toolCallId": "call_github_poll_after",
54+
"model": "gpt-5.4-mini",
55+
"endpoint": "chat"
56+
},
57+
"response": {
58+
"content": "GitHub poll plugin follow-up complete."
59+
}
60+
}
61+
]
62+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{
2+
"fixtures": [
3+
{
4+
"match": {
5+
"userMessage": "Call the hot reload plugin before edit.",
6+
"model": "gpt-5.4-mini",
7+
"endpoint": "chat",
8+
"hasToolResult": false
9+
},
10+
"response": {
11+
"toolCalls": [
12+
{
13+
"id": "call_hot_reload_before",
14+
"name": "e2e_plugin_lookup",
15+
"arguments": {
16+
"query": "before"
17+
}
18+
}
19+
]
20+
}
21+
},
22+
{
23+
"match": {
24+
"hasToolResult": true,
25+
"toolCallId": "call_hot_reload_before",
26+
"model": "gpt-5.4-mini",
27+
"endpoint": "chat"
28+
},
29+
"response": {
30+
"content": "Hot reload plugin returned version-one."
31+
}
32+
},
33+
{
34+
"match": {
35+
"model": "gpt-5.4-mini",
36+
"endpoint": "chat"
37+
},
38+
"response": {
39+
"toolCalls": [
40+
{
41+
"id": "call_hot_reload_after",
42+
"name": "e2e_plugin_lookup",
43+
"arguments": {
44+
"query": "after"
45+
}
46+
}
47+
]
48+
}
49+
},
50+
{
51+
"match": {
52+
"hasToolResult": true,
53+
"toolCallId": "call_hot_reload_after",
54+
"model": "gpt-5.4-mini",
55+
"endpoint": "chat"
56+
},
57+
"response": {
58+
"content": "Hot reload plugin follow-up complete."
59+
}
60+
}
61+
]
62+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"fixtures": [
3+
{
4+
"match": {
5+
"endpoint": "chat",
6+
"model": "gpt-5.4-mini",
7+
"userMessage": "Use the local plugin tool availability check."
8+
},
9+
"response": {
10+
"content": "Plugin tool availability verified."
11+
}
12+
}
13+
]
14+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"fixtures": [
3+
{
4+
"match": {
5+
"userMessage": "Use the scaffolded example plugin tool.",
6+
"model": "gpt-5.4-mini",
7+
"endpoint": "chat",
8+
"hasToolResult": false
9+
},
10+
"response": {
11+
"toolCalls": [
12+
{
13+
"id": "call_scaffolded_example_tool",
14+
"name": "example_tool",
15+
"arguments": {
16+
"message": "hello from scaffold"
17+
}
18+
}
19+
]
20+
}
21+
},
22+
{
23+
"match": {
24+
"hasToolResult": true,
25+
"toolCallId": "call_scaffolded_example_tool",
26+
"model": "gpt-5.4-mini",
27+
"endpoint": "chat"
28+
},
29+
"response": {
30+
"content": "Scaffolded example tool returned hello from scaffold."
31+
}
32+
}
33+
]
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"fixtures": [
3+
{
4+
"match": {
5+
"userMessage": "Use the streaming plugin tool.",
6+
"model": "gpt-5.4-mini",
7+
"endpoint": "chat",
8+
"hasToolResult": false
9+
},
10+
"response": {
11+
"toolCalls": [
12+
{
13+
"id": "call_streaming_plugin_tool",
14+
"name": "e2e_plugin_lookup",
15+
"arguments": {
16+
"query": "stream progress"
17+
}
18+
}
19+
]
20+
}
21+
},
22+
{
23+
"match": {
24+
"hasToolResult": true,
25+
"toolCallId": "call_streaming_plugin_tool",
26+
"model": "gpt-5.4-mini",
27+
"endpoint": "chat"
28+
},
29+
"response": {
30+
"content": "Streaming plugin tool completed."
31+
}
32+
}
33+
]
34+
}

mastracode/e2e/terminal-backend.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ async function startMastraCodeApp(
371371
hookManager: result.hookManager,
372372
authStorage: result.authStorage,
373373
mcpManager: result.mcpManager,
374+
pluginManager: result.pluginManager,
374375
appName: 'Mastra Code',
375376
version: process.env.npm_package_version ?? 'mc-e2e-terminal',
376377
inlineQuestions: true,

mastracode/e2e/tui/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ import { persistentGoalReloadScenario } from './persistent-goal-reload.js';
7373
import { planApprovalGoalHandoffScenario } from './plan-approval-goal-handoff.js';
7474
import { planApprovalHandoffScenario } from './plan-approval-handoff.js';
7575
import { planApprovalRequestChangesScenario } from './plan-approval-request-changes.js';
76+
import {
77+
pluginsAssetsLoadingScenario,
78+
pluginsBlockedConfigScenario,
79+
pluginsCommandUiScenario,
80+
pluginsGithubPollUpdateScenario,
81+
pluginsLocalHotReloadScenario,
82+
pluginsLocalToolScenario,
83+
pluginsScaffoldInstallToolScenario,
84+
pluginsStreamingToolOutputScenario,
85+
} from './plugins.js';
7686
import { processShortcutsScenario } from './process-shortcuts.js';
7787
import { promptContextInstructionsScenario } from './prompt-context-instructions.js';
7888
import { promptQueueInterleaveScenario } from './prompt-queue-interleave.js';
@@ -209,6 +219,14 @@ export const scenarios: Record<ScenarioName, McE2eScenario> = {
209219
'plan-approval-goal-handoff': planApprovalGoalHandoffScenario,
210220
'plan-approval-handoff': planApprovalHandoffScenario,
211221
'plan-approval-request-changes': planApprovalRequestChangesScenario,
222+
'plugins-local-tool': pluginsLocalToolScenario,
223+
'plugins-local-hot-reload': pluginsLocalHotReloadScenario,
224+
'plugins-github-poll-update': pluginsGithubPollUpdateScenario,
225+
'plugins-blocked-config': pluginsBlockedConfigScenario,
226+
'plugins-scaffold-install-tool': pluginsScaffoldInstallToolScenario,
227+
'plugins-streaming-tool-output': pluginsStreamingToolOutputScenario,
228+
'plugins-assets-loading': pluginsAssetsLoadingScenario,
229+
'plugins-command-ui': pluginsCommandUiScenario,
212230
'process-shortcuts': processShortcutsScenario,
213231
'provider-history-compat': providerHistoryCompatScenario,
214232
'provider-history-rejection-retry': providerHistoryRejectionRetryScenario,

0 commit comments

Comments
 (0)