@mastra/mcp-apps-components 0.0.0-breadcrumb-chevrons-20261105145508

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 (39) hide show
  1. package/LICENSE.md +32 -0
  2. package/README.md +187 -0
  3. package/dist/apps/dashboard-renderer/data-tool.d.ts +22 -0
  4. package/dist/apps/dashboard-renderer/data-tool.d.ts.map +1 -0
  5. package/dist/apps/dashboard-renderer/data.d.ts +54 -0
  6. package/dist/apps/dashboard-renderer/data.d.ts.map +1 -0
  7. package/dist/apps/dashboard-renderer/input.d.ts +1407 -0
  8. package/dist/apps/dashboard-renderer/input.d.ts.map +1 -0
  9. package/dist/apps/dashboard-renderer/pin.d.ts +2025 -0
  10. package/dist/apps/dashboard-renderer/pin.d.ts.map +1 -0
  11. package/dist/dashboard-data-tool.cjs +56 -0
  12. package/dist/dashboard-data-tool.cjs.map +1 -0
  13. package/dist/dashboard-data-tool.d.ts +2 -0
  14. package/dist/dashboard-data-tool.d.ts.map +1 -0
  15. package/dist/dashboard-data-tool.js +54 -0
  16. package/dist/dashboard-data-tool.js.map +1 -0
  17. package/dist/dashboard-renderer.cjs +64 -0
  18. package/dist/dashboard-renderer.cjs.map +1 -0
  19. package/dist/dashboard-renderer.d.ts +17 -0
  20. package/dist/dashboard-renderer.d.ts.map +1 -0
  21. package/dist/dashboard-renderer.js +53 -0
  22. package/dist/dashboard-renderer.js.map +1 -0
  23. package/dist/data-Ctrne5-k.cjs +99 -0
  24. package/dist/data-Ctrne5-k.cjs.map +1 -0
  25. package/dist/data-DWr8W2w1.js +70 -0
  26. package/dist/data-DWr8W2w1.js.map +1 -0
  27. package/dist/define-mcp-app-resource.d.ts +16 -0
  28. package/dist/define-mcp-app-resource.d.ts.map +1 -0
  29. package/dist/host.cjs +66 -0
  30. package/dist/host.cjs.map +1 -0
  31. package/dist/host.js +61 -0
  32. package/dist/host.js.map +1 -0
  33. package/dist/pin-Cgnadk90.js +46 -0
  34. package/dist/pin-Cgnadk90.js.map +1 -0
  35. package/dist/pin-DQfVIWW4.cjs +69 -0
  36. package/dist/pin-DQfVIWW4.cjs.map +1 -0
  37. package/dist/types.d.ts +9 -0
  38. package/dist/types.d.ts.map +1 -0
  39. package/package.json +114 -0
package/LICENSE.md ADDED
@@ -0,0 +1,32 @@
1
+ Portions of this software are licensed as follows:
2
+
3
+ - All content that resides under any directory named `ee/` within this
4
+ repository, including but not limited to:
5
+ - `@mastra/core/auth/ee`
6
+ - `@mastra/core/agent-builder/ee`
7
+ - `@mastra/editor/ee`
8
+
9
+ is licensed under the license defined in [`ee/LICENSE`](https://github.com/mastra-ai/mastra/blob/main/ee/LICENSE).
10
+
11
+ - All third-party components incorporated into the Mastra Software are
12
+ licensed under the original license provided by the owner of the
13
+ applicable component.
14
+
15
+ - Content outside of the above-mentioned directories or restrictions is
16
+ available under the "Apache License 2.0" as defined below.
17
+
18
+ # Apache License 2.0
19
+
20
+ Copyright (c) 2025 Kepler Software, Inc.
21
+
22
+ Licensed under the Apache License, Version 2.0 (the "License");
23
+ you may not use this file except in compliance with the License.
24
+ You may obtain a copy of the License at
25
+
26
+ http://www.apache.org/licenses/LICENSE-2.0
27
+
28
+ Unless required by applicable law or agreed to in writing, software
29
+ distributed under the License is distributed on an "AS IS" BASIS,
30
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31
+ See the License for the specific language governing permissions and
32
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # @mastra/mcp-apps-components
2
+
3
+ Prebuilt [MCP Apps](https://mastra.ai/docs/connections/mcp) that render Mastra UI components inside any MCP App host, such as Mastra Studio. Each app ships as a self-contained HTML resource you register on an `MCPServer`, plus a server-side helper to link it to your tools.
4
+
5
+ The apps never reach the network from their iframe. Each read they make travels as a call to one app-only data tool that you register on the same server.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @mastra/mcp-apps-components
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Register the dashboard renderer on an `MCPServer`, then return a dashboard definition from a tool linked to the app's `ui://` URI.
16
+
17
+ ```typescript
18
+ import type { DashboardDefinition } from '@mastra/core/storage/dashboards';
19
+ import { createTool } from '@mastra/core/tools';
20
+ import { MCPServer } from '@mastra/mcp';
21
+ import { dashboardDataTool } from '@mastra/mcp-apps-components/dashboard-data-tool';
22
+ import { dashboardRendererApp, dashboardRendererInputSchema } from '@mastra/mcp-apps-components/dashboard-renderer';
23
+ import { z } from 'zod';
24
+
25
+ const DASHBOARD_APP_URI = 'ui://mastra/dashboard';
26
+
27
+ const agentRuns: DashboardDefinition = {
28
+ schemaVersion: 1,
29
+ defaults: { datePreset: '7d' },
30
+ rows: [
31
+ {
32
+ widgets: [
33
+ {
34
+ id: 'runs',
35
+ span: 12,
36
+ kind: 'kpis',
37
+ views: [
38
+ {
39
+ queries: [
40
+ {
41
+ id: 'runs',
42
+ source: 'metrics',
43
+ request: 'aggregate',
44
+ name: ['mastra_agent_duration_ms'],
45
+ aggregation: 'count',
46
+ },
47
+ ],
48
+ options: { items: [{ label: 'Agent runs', format: 'count', value: { query: 'runs' } }] },
49
+ },
50
+ ],
51
+ },
52
+ ],
53
+ },
54
+ ],
55
+ };
56
+
57
+ const showAgentRuns = createTool({
58
+ id: 'showAgentRuns',
59
+ description: 'Shows how many agent runs happened in the last 7 days',
60
+ inputSchema: z.object({}),
61
+ outputSchema: dashboardRendererInputSchema,
62
+ mcp: { _meta: { ui: { resourceUri: DASHBOARD_APP_URI } } },
63
+ execute: async () => ({ definition: agentRuns, scope: 'project' as const }),
64
+ });
65
+
66
+ export const dashboardServer = new MCPServer({
67
+ id: 'dashboard-server',
68
+ name: 'Dashboard Server',
69
+ version: '1.0.0',
70
+ appResources: { [DASHBOARD_APP_URI]: dashboardRendererApp() },
71
+ tools: { showAgentRuns, dashboardDataTool },
72
+ });
73
+ ```
74
+
75
+ ### Data tool
76
+
77
+ Register `dashboardDataTool`, from `@mastra/mcp-apps-components/dashboard-data-tool`, under that exact key, next to the app. The app calls it by name for every read its dashboard makes, such as `getMetricAggregate`. Without it, each widget fails with a message telling you to register it.
78
+
79
+ The tool runs the observability route behind each read in-process as the caller, with `invokeRoute()` from `@mastra/server`, so the route's own validation, authentication, permission and FGA checks apply and the app sees the same data the signed-in user can. It only answers the reads in `DASHBOARD_DATA_OPERATIONS`, and it's app-only, so hosts keep it away from the model.
80
+
81
+ Only the `dashboard-data-tool` entry needs `@mastra/server`, which any Mastra server already has. When the server has authentication configured, reads made over an MCP transport that carries no signed-in user, such as stdio, answer with `401`.
82
+
83
+ ### Answering the data tool yourself
84
+
85
+ A host that reads observability data its own way, such as one serving several projects, can register its own `dashboardDataTool` instead. Import the contract from `@mastra/mcp-apps-components/dashboard-renderer`, which doesn't load `@mastra/server`: `DASHBOARD_DATA_TOOL_NAME`, `DASHBOARD_DATA_OPERATIONS`, `DASHBOARD_DATA_REQUESTS`, `dashboardDataInputSchema` and `dashboardDataOutputSchema`.
86
+
87
+ To tell the tool which project to read, set `projectId` in the dashboard's tool result. The app sends it as `projectId` on every data tool call. Mastra's `dashboardDataTool` reads the one project its server holds and ignores it.
88
+
89
+ ### Pinning widgets
90
+
91
+ Each widget shows a pin button when the host says it can pin. The host owns the pinned widgets; the app only reports which ones the user wants. Everything goes through standard MCP Apps messages.
92
+
93
+ 1. The host sets `mastra.ai/dashboard-pinned-widgets` in its host context to the ids of the widgets it has pinned (`[]` for none) and advertises `updateModelContext.structuredContent`. Without both, no pin buttons show.
94
+ 2. When the user clicks a widget's button, the app sends `ui/update-model-context` with the **full list** of widgets the user wants pinned under the same key in `structuredContent`. Each entry is `{ widget, scope, defaults? }`: the widget definition, the dashboard's scope, and the date range and filters the user had on, which together render the pinned widget on its own as the user saw it. Only widgets pinned since the app loaded carry `defaults`; for the rest, keep the defaults you stored. The update also has a short text block describing the pins, so the model sees them too, and a request number under `mastra.ai/dashboard-pin-request`.
95
+ 3. The host stores what it accepts and sends back, in its host context, the new list of ids and the request number it handled. Each button follows the host, not the click.
96
+
97
+ Every model context update replaces the previous one, so each update the dashboard sends carries both its pin state and its last drilldown. An update without the pin key carries no pin state; it never means "nothing pinned".
98
+
99
+ #### In a React host built on `@mastra/playground-ui`
100
+
101
+ `useMcpAppHost` from the `@mastra/mcp-apps-components/host` subpath does the parsing and routing. That subpath doesn't include the app's HTML, so it adds nothing heavy to the host bundle.
102
+
103
+ ```tsx
104
+ import { useCallback, useState } from 'react';
105
+ import { McpAppHostProvider } from '@mastra/playground-ui/domains/mcps';
106
+ import { useMcpAppHost } from '@mastra/mcp-apps-components/host';
107
+ import type { McpAppRef, DashboardPinnedWidgetEntry } from '@mastra/mcp-apps-components/host';
108
+
109
+ function ChatWithPins({ children }: { children: React.ReactNode }) {
110
+ // Widget ids are only unique within one dashboard, so key them by tool call.
111
+ const [pins, setPins] = useState<Record<string, string[]>>({});
112
+
113
+ const pinnedIds = useCallback(
114
+ (app: McpAppRef) => (app.resourceUri === DASHBOARD_APP_URI ? (pins[app.toolCallId] ?? []) : undefined),
115
+ [pins],
116
+ );
117
+ const onPinnedChange = async (pinned: DashboardPinnedWidgetEntry[], app: McpAppRef) => {
118
+ // Full list, not a change: diff it, return the ids you kept. If this throws, the pins count as turned down.
119
+ const ids = await savePinnedWidgets(app.toolCallId, pinned);
120
+ setPins(current => ({ ...current, [app.toolCallId]: ids }));
121
+ };
122
+
123
+ const host = useMcpAppHost({ dashboardPins: { pinnedIds, onPinnedChange } });
124
+ return <McpAppHostProvider value={host}>{children}</McpAppHostProvider>;
125
+ }
126
+ ```
127
+
128
+ `pinnedIds` returns `[]` for none, `null` to turn pinning off for an app, or `undefined` for apps that aren't dashboards. The hook echoes each handled request number back for you once `onPinnedChange` returns or its promise settles, so update `pinnedIds` by then; ids you don't add are pins you turned down. If `onPinnedChange` throws or rejects, the error is logged and the pins count as turned down. Updates from one app instance wait for the previous `onPinnedChange` to settle, so an older save never lands after a newer one. Invalid payloads are dropped and logged, never passed to `onPinnedChange`.
129
+
130
+ #### With `AppBridge` directly
131
+
132
+ The params come from the app's iframe, so parse them with `dashboardPinnedWidgetsSchema` before storing or rendering anything.
133
+
134
+ ```typescript
135
+ import { AppBridge } from '@modelcontextprotocol/ext-apps/app-bridge';
136
+ import {
137
+ DASHBOARD_PIN_REQUEST_CONTEXT_KEY,
138
+ DASHBOARD_PINNED_WIDGETS_CONTEXT_KEY,
139
+ dashboardPinnedWidgetsSchema,
140
+ } from '@mastra/mcp-apps-components/dashboard-renderer';
141
+
142
+ let hostContext = { theme: 'dark' as const, [DASHBOARD_PINNED_WIDGETS_CONTEXT_KEY]: [] as string[] };
143
+ const bridge = new AppBridge(
144
+ client,
145
+ hostInfo,
146
+ { ...capabilities, updateModelContext: { text: {}, structuredContent: {} } },
147
+ { hostContext },
148
+ );
149
+
150
+ bridge.onupdatemodelcontext = async ({ structuredContent }) => {
151
+ if (!structuredContent || !(DASHBOARD_PINNED_WIDGETS_CONTEXT_KEY in structuredContent)) return {};
152
+ const parsed = dashboardPinnedWidgetsSchema.safeParse(structuredContent[DASHBOARD_PINNED_WIDGETS_CONTEXT_KEY]);
153
+ if (!parsed.success) return {};
154
+
155
+ const ids = await savePinnedWidgets(parsed.data); // full list: diff it, return the ids you kept
156
+ // setHostContext replaces the whole context, so keep the other fields. Echo the request number,
157
+ // so the app knows you answered even when you turned a pin down and the ids stay the same.
158
+ hostContext = {
159
+ ...hostContext,
160
+ [DASHBOARD_PINNED_WIDGETS_CONTEXT_KEY]: ids,
161
+ [DASHBOARD_PIN_REQUEST_CONTEXT_KEY]: structuredContent[DASHBOARD_PIN_REQUEST_CONTEXT_KEY],
162
+ };
163
+ bridge.setHostContext(hostContext);
164
+ return {};
165
+ };
166
+ ```
167
+
168
+ Keep in mind:
169
+
170
+ - To turn pinning off, set the key to a non-array value such as `null`. Deleting the key does nothing, because `AppBridge` only sends the fields that changed, so the app keeps the last ids it got.
171
+ - Widget ids are only unique within one dashboard definition. Keep pinned ids per bridge (one tool call), not across dashboards.
172
+ - Until the host answers a click, the app applies it on top of the host's ids, so quick clicks don't drop pins. A click is answered once the host echoes a request number at or above the one it was sent with, or once the host's ids already reflect it. A pin the host declines then drops, and the next click doesn't resend it.
173
+ - If the app's model context update fails, its clicks drop, and the buttons show the host's ids again.
174
+ - A widget that no longer parses shows no pin button, so the host owns unpinning it.
175
+
176
+ ## Documentation
177
+
178
+ - [Build MCP Apps](https://mastra.ai/docs/connections/mcp)
179
+ - [`MCPServer` reference](https://mastra.ai/reference/tools/mcp-server)
180
+
181
+ ## Changelog
182
+
183
+ See the [package changelog](https://github.com/mastra-ai/mastra/blob/main/packages/mcp-apps-components/CHANGELOG.md) for version history and release notes.
184
+
185
+ ## Support
186
+
187
+ We have an [open community Discord](https://discord.gg/mastra-ai). Come and say hello and let us know if you have any questions or need any help getting things running.
@@ -0,0 +1,22 @@
1
+ import type { ServerRoute } from '@mastra/server/server-adapter';
2
+ import type { DashboardDataOperation } from './data.js';
3
+ /** The Mastra route that answers each read. */
4
+ export declare const DASHBOARD_DATA_ROUTES: Record<DashboardDataOperation, ServerRoute>;
5
+ /**
6
+ * The app-only tool the dashboard reads its data through. Register it next to
7
+ * `dashboardRendererApp` on the MCP server that serves the app. Each read runs its Mastra route
8
+ * in-process as the caller, with the route's own input validation, authentication, RBAC permission
9
+ * and FGA checks, so the app sees exactly the data the signed-in user can read over HTTP. It runs
10
+ * only the routes in `DASHBOARD_DATA_ROUTES`. It reads the one project its server holds, so it
11
+ * ignores a call's `projectId`.
12
+ */
13
+ export declare const dashboardDataTool: import("@mastra/core/tools").Tool<{
14
+ operation: "aggregateSpans" | "aggregateTraces" | "getMetricAggregate" | "getMetricBreakdown" | "getMetricPercentiles" | "getMetricTimeSeries" | "getScoreAggregate" | "getScoreBreakdown" | "getScorePercentiles" | "getScoreTimeSeries" | "listScores";
15
+ query?: Record<string, string | string[]> | undefined;
16
+ body?: Record<string, unknown> | undefined;
17
+ projectId?: string | undefined;
18
+ }, {
19
+ status: number;
20
+ body: unknown;
21
+ }, unknown, unknown, import("@mastra/core/tools").ToolExecutionContext<unknown, unknown, unknown>, "dashboardDataTool", unknown>;
22
+ //# sourceMappingURL=data-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-tool.d.ts","sourceRoot":"","sources":["../../../src/apps/dashboard-renderer/data-tool.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,KAAK,EAAE,sBAAsB,EAAuB,MAAM,QAAQ,CAAC;AAE1E,+CAA+C;AAC/C,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,sBAAsB,EAAE,WAAW,CAY7E,CAAC;AAOF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;gIA0B5B,CAAC"}
@@ -0,0 +1,54 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * The app-only tool the dashboard reads its data through. The app runs in a sandboxed iframe that
4
+ * hosts may forbid from reaching the server, so each read travels as a call to this tool over the
5
+ * host's existing MCP connection. An MCP server names a tool by the key it's registered under, so
6
+ * this matches the export: `tools: { dashboardDataTool }`.
7
+ */
8
+ export declare const DASHBOARD_DATA_TOOL_NAME = "dashboardDataTool";
9
+ /** Every read the dashboard makes, named after the `MastraClient` method that makes it. */
10
+ export declare const DASHBOARD_DATA_OPERATIONS: readonly ['aggregateTraces', 'aggregateSpans', 'getMetricAggregate', 'getMetricBreakdown', 'getMetricTimeSeries', 'getMetricPercentiles', 'listScores', 'getScoreAggregate', 'getScoreBreakdown', 'getScoreTimeSeries', 'getScorePercentiles'];
11
+ export type DashboardDataOperation = (typeof DASHBOARD_DATA_OPERATIONS)[number];
12
+ /**
13
+ * The Mastra route behind each read: the request `MastraClient` makes for it, without the server's
14
+ * API prefix. A server that answers the data tool with its own routes can look them up here.
15
+ */
16
+ export declare const DASHBOARD_DATA_REQUESTS: {
17
+ aggregateTraces: string;
18
+ aggregateSpans: string;
19
+ getMetricAggregate: string;
20
+ getMetricBreakdown: string;
21
+ getMetricTimeSeries: string;
22
+ getMetricPercentiles: string;
23
+ listScores: string;
24
+ getScoreAggregate: string;
25
+ getScoreBreakdown: string;
26
+ getScoreTimeSeries: string;
27
+ getScorePercentiles: string;
28
+ };
29
+ export declare const dashboardDataInputSchema: z.ZodObject<{
30
+ operation: z.ZodEnum<{
31
+ aggregateSpans: "aggregateSpans";
32
+ aggregateTraces: "aggregateTraces";
33
+ getMetricAggregate: "getMetricAggregate";
34
+ getMetricBreakdown: "getMetricBreakdown";
35
+ getMetricPercentiles: "getMetricPercentiles";
36
+ getMetricTimeSeries: "getMetricTimeSeries";
37
+ getScoreAggregate: "getScoreAggregate";
38
+ getScoreBreakdown: "getScoreBreakdown";
39
+ getScorePercentiles: "getScorePercentiles";
40
+ getScoreTimeSeries: "getScoreTimeSeries";
41
+ listScores: "listScores";
42
+ }>;
43
+ query: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>>;
44
+ body: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
45
+ projectId: z.ZodOptional<z.ZodString>;
46
+ }, z.core.$strip>;
47
+ export type DashboardDataInput = z.output<typeof dashboardDataInputSchema>;
48
+ /** What the tool answers: a status and body, as the matching Mastra route would over HTTP. */
49
+ export declare const dashboardDataOutputSchema: z.ZodObject<{
50
+ status: z.ZodNumber;
51
+ body: z.ZodUnknown;
52
+ }, z.core.$strip>;
53
+ export type DashboardDataOutput = z.output<typeof dashboardDataOutputSchema>;
54
+ //# sourceMappingURL=data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../../src/apps/dashboard-renderer/data.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,sBAAsB,CAAC;AAE5D,2FAA2F;AAC3F,eAAO,MAAM,yBAAyB,YACpC,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,CACb,CAAC;AAEX,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhF;;;GAGG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;CAYc,CAAC;AAInD,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;iBAmBjC,CAAC;AAEL,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAK3E,8FAA8F;AAC9F,eAAO,MAAM,yBAAyB;;;iBAQpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,yBAAyB,CAAC,CAAC"}