@mastra/mcp 1.6.1-alpha.1 → 1.7.0
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/CHANGELOG.md +100 -0
- package/dist/client/client.d.ts +1 -0
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/configuration.d.ts +26 -0
- package/dist/client/configuration.d.ts.map +1 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/server-proxy.d.ts +88 -0
- package/dist/client/server-proxy.d.ts.map +1 -0
- package/dist/docs/SKILL.md +2 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-mcp-mcp-apps.md +304 -0
- package/dist/docs/references/docs-mcp-overview.md +7 -0
- package/dist/docs/references/reference-tools-mcp-client.md +32 -0
- package/dist/docs/references/reference-tools-mcp-server.md +64 -0
- package/dist/index.cjs +348 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +348 -7
- package/dist/index.js.map +1 -1
- package/dist/server/server.d.ts +59 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/types.d.ts +37 -0
- package/dist/server/types.d.ts.map +1 -1
- package/package.json +8 -7
|
@@ -126,6 +126,38 @@ Disconnects from all MCP servers and cleans up resources.
|
|
|
126
126
|
async disconnect(): Promise<void>
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
+
### `toMCPServerProxies()`
|
|
130
|
+
|
|
131
|
+
Returns a map of `MCPClientServerProxy` instances, one per configured server. Each proxy wraps the underlying client connection as an `MCPServerBase` instance, allowing external (non-Mastra) MCP servers to be registered in `mcpServers` and appear in Studio.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
async toMCPServerProxies(): Promise<Record<string, MCPClientServerProxy>>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Spread the result into the `mcpServers` config on `Mastra`:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { Mastra } from '@mastra/core/mastra'
|
|
141
|
+
import { MCPClient } from '@mastra/mcp'
|
|
142
|
+
|
|
143
|
+
const mcpClient = new MCPClient({
|
|
144
|
+
servers: {
|
|
145
|
+
'color-mixer': {
|
|
146
|
+
command: 'node',
|
|
147
|
+
args: ['path/to/color-mixer-server.js'],
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
export const mastra = new Mastra({
|
|
153
|
+
mcpServers: {
|
|
154
|
+
...(await mcpClient.toMCPServerProxies()),
|
|
155
|
+
},
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
This is useful for connecting external MCP servers that implement the MCP Apps extension or other features to Studio without wrapping them in a Mastra `MCPServer`.
|
|
160
|
+
|
|
129
161
|
### `resources` Property
|
|
130
162
|
|
|
131
163
|
The `MCPClient` instance has a `resources` property that provides access to resource-related operations.
|
|
@@ -83,6 +83,8 @@ The constructor accepts an `MCPServerConfig` object with the following propertie
|
|
|
83
83
|
|
|
84
84
|
**prompts** (`MCPServerPrompts`): An object defining how the server should handle MCP prompts. See Prompt Handling section for details.
|
|
85
85
|
|
|
86
|
+
**appResources** (`AppResources`): A map of \`ui://\` URIs to app resource configurations. Each entry defines an interactive HTML UI served via the MCP Apps extension (SEP-1865). See the MCP Apps section for details.
|
|
87
|
+
|
|
86
88
|
## Exposing agents as tools
|
|
87
89
|
|
|
88
90
|
A powerful feature of `MCPServer` is its ability to automatically expose your Mastra Agents as callable tools. When you provide agents in the `agents` property of the configuration:
|
|
@@ -1263,6 +1265,68 @@ app.all('/mcp', async (req, res) => {
|
|
|
1263
1265
|
app.listen(3000)
|
|
1264
1266
|
```
|
|
1265
1267
|
|
|
1268
|
+
## MCP Apps (`appResources`)
|
|
1269
|
+
|
|
1270
|
+
The `appResources` option lets you serve interactive HTML UIs from your MCP server via the [MCP Apps extension](https://github.com/modelcontextprotocol/ext-apps). Each entry maps a `ui://` URI to an HTML app that renders in a sandboxed iframe in Mastra Studio.
|
|
1271
|
+
|
|
1272
|
+
### `AppResources` type
|
|
1273
|
+
|
|
1274
|
+
**Key (URI)** (`string`): A \`ui://\` URI that identifies the app resource (e.g., \`ui://calculator/main\`).
|
|
1275
|
+
|
|
1276
|
+
Each value is an `AppResource` object:
|
|
1277
|
+
|
|
1278
|
+
**name** (`string`): Display name for the UI resource.
|
|
1279
|
+
|
|
1280
|
+
**description** (`string`): Optional description of the UI resource.
|
|
1281
|
+
|
|
1282
|
+
**html** (`string`): Inline HTML content for the UI. Provide either \`html\` or \`htmlPath\`.
|
|
1283
|
+
|
|
1284
|
+
**htmlPath** (`string`): Path to an HTML file. Resolved at server startup. Provide either \`html\` or \`htmlPath\`.
|
|
1285
|
+
|
|
1286
|
+
**meta** (`McpUiResourceMeta`): UI resource metadata (CSP, permissions, rendering preferences) from the official ext-apps SDK.
|
|
1287
|
+
|
|
1288
|
+
### Example
|
|
1289
|
+
|
|
1290
|
+
```typescript
|
|
1291
|
+
import { MCPServer } from '@mastra/mcp'
|
|
1292
|
+
import { createTool } from '@mastra/core/tools'
|
|
1293
|
+
import { z } from 'zod'
|
|
1294
|
+
|
|
1295
|
+
const calculatorTool = createTool({
|
|
1296
|
+
id: 'calculatorWithUI',
|
|
1297
|
+
description: 'An interactive calculator',
|
|
1298
|
+
inputSchema: z.object({
|
|
1299
|
+
num1: z.number(),
|
|
1300
|
+
num2: z.number(),
|
|
1301
|
+
operation: z.enum(['add', 'subtract']),
|
|
1302
|
+
}),
|
|
1303
|
+
execute: async ({ num1, num2, operation }) => {
|
|
1304
|
+
const result = operation === 'add' ? num1 + num2 : num1 - num2
|
|
1305
|
+
return {
|
|
1306
|
+
content: [{ type: 'text', text: 'An interactive calculator is displayed.' }],
|
|
1307
|
+
structuredContent: { result },
|
|
1308
|
+
}
|
|
1309
|
+
},
|
|
1310
|
+
})
|
|
1311
|
+
|
|
1312
|
+
const server = new MCPServer({
|
|
1313
|
+
id: 'app-server',
|
|
1314
|
+
name: 'App Server',
|
|
1315
|
+
version: '1.0.0',
|
|
1316
|
+
tools: { calculatorTool },
|
|
1317
|
+
appResources: {
|
|
1318
|
+
'ui://calculator/main': {
|
|
1319
|
+
name: 'Interactive Calculator',
|
|
1320
|
+
html: '<html><body><h2>Calculator</h2>...</body></html>',
|
|
1321
|
+
},
|
|
1322
|
+
},
|
|
1323
|
+
})
|
|
1324
|
+
```
|
|
1325
|
+
|
|
1326
|
+
Link a tool to its app resource by setting `_meta.ui.resourceUri` on the tool to the matching `ui://` URI. The server auto-normalizes this metadata when registering tools.
|
|
1327
|
+
|
|
1328
|
+
> **Note:** Visit [MCP Apps](https://mastra.ai/docs/mcp/mcp-apps) for the full app bridge API and usage patterns.
|
|
1329
|
+
|
|
1266
1330
|
## Related information
|
|
1267
1331
|
|
|
1268
1332
|
- For connecting to MCP servers in Mastra, see the [MCPClient documentation](https://mastra.ai/reference/tools/mcp-client).
|
package/dist/index.cjs
CHANGED
|
@@ -15,9 +15,11 @@ var crypto$1 = require('crypto');
|
|
|
15
15
|
var error = require('@mastra/core/error');
|
|
16
16
|
var equal = require('fast-deep-equal');
|
|
17
17
|
var mcp = require('@mastra/core/mcp');
|
|
18
|
-
var requestContext = require('@mastra/core/request-context');
|
|
19
18
|
var schema = require('@mastra/core/schema');
|
|
19
|
+
var fs = require('fs');
|
|
20
|
+
var requestContext = require('@mastra/core/request-context');
|
|
20
21
|
var utils = require('@mastra/core/utils');
|
|
22
|
+
var extApps = require('@modelcontextprotocol/ext-apps');
|
|
21
23
|
var index_js$1 = require('@modelcontextprotocol/sdk/server/index.js');
|
|
22
24
|
var sse_js$1 = require('@modelcontextprotocol/sdk/server/sse.js');
|
|
23
25
|
var stdio_js$1 = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
@@ -542,7 +544,12 @@ var InternalMastraMCPClient = class extends base.MastraBase {
|
|
|
542
544
|
...capabilities.elicitation ?? {}
|
|
543
545
|
},
|
|
544
546
|
// Auto-enable roots capability if roots are provided
|
|
545
|
-
...hasRoots ? { roots: { listChanged: true, ...capabilities.roots ?? {} } } : {}
|
|
547
|
+
...hasRoots ? { roots: { listChanged: true, ...capabilities.roots ?? {} } } : {},
|
|
548
|
+
// Advertise MCP Apps extension support so servers know we can render UI resources
|
|
549
|
+
extensions: {
|
|
550
|
+
...capabilities.extensions ?? {},
|
|
551
|
+
"io.modelcontextprotocol/ui": {}
|
|
552
|
+
}
|
|
546
553
|
};
|
|
547
554
|
this.client = new index_js.Client(
|
|
548
555
|
{
|
|
@@ -975,11 +982,16 @@ var InternalMastraMCPClient = class extends base.MastraBase {
|
|
|
975
982
|
} else if (this.requireToolApproval === true) {
|
|
976
983
|
requireApproval = true;
|
|
977
984
|
}
|
|
985
|
+
const rawMeta = tool._meta;
|
|
986
|
+
const toolMeta = rawMeta ? this.stampServerIdInMeta(rawMeta) : void 0;
|
|
978
987
|
const mastraTool = tools.createTool({
|
|
979
988
|
id: `${this.name}_${tool.name}`,
|
|
980
989
|
description: tool.description || "",
|
|
981
990
|
inputSchema: await this.convertInputSchema(tool.inputSchema),
|
|
982
|
-
strict: getMastraToolStrictMeta(
|
|
991
|
+
strict: getMastraToolStrictMeta(toolMeta),
|
|
992
|
+
// Preserve the full _meta from the remote MCP server (including ui.resourceUri
|
|
993
|
+
// for MCP Apps) so downstream consumers (e.g. Studio) can detect app tools.
|
|
994
|
+
...toolMeta ? { mcp: { _meta: toolMeta } } : {},
|
|
983
995
|
// Don't pass outputSchema to createTool — the MCP SDK's Client.callTool()
|
|
984
996
|
// already validates structuredContent against the tool's outputSchema using AJV.
|
|
985
997
|
// Passing it here causes Zod to strip unrecognized keys from the CallToolResult
|
|
@@ -1059,7 +1071,152 @@ var InternalMastraMCPClient = class extends base.MastraBase {
|
|
|
1059
1071
|
}
|
|
1060
1072
|
return toolsRes;
|
|
1061
1073
|
}
|
|
1074
|
+
stampServerIdInMeta(meta) {
|
|
1075
|
+
const ui = meta.ui;
|
|
1076
|
+
if (!ui?.resourceUri) return meta;
|
|
1077
|
+
return {
|
|
1078
|
+
...meta,
|
|
1079
|
+
ui: { ...ui, serverId: this.name }
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
var MCPClientServerProxy = class extends mcp.MCPServerBase {
|
|
1084
|
+
clientGetter;
|
|
1085
|
+
cachedClient = null;
|
|
1086
|
+
_cachedToolList = null;
|
|
1087
|
+
constructor(config, clientGetter) {
|
|
1088
|
+
const serverConfig = {
|
|
1089
|
+
name: config.name,
|
|
1090
|
+
version: config.version ?? "1.0.0",
|
|
1091
|
+
id: config.id,
|
|
1092
|
+
description: config.description,
|
|
1093
|
+
tools: {}
|
|
1094
|
+
};
|
|
1095
|
+
super(serverConfig);
|
|
1096
|
+
this.clientGetter = clientGetter;
|
|
1097
|
+
}
|
|
1098
|
+
async getClient() {
|
|
1099
|
+
if (!this.cachedClient) {
|
|
1100
|
+
this.cachedClient = await this.clientGetter();
|
|
1101
|
+
}
|
|
1102
|
+
return this.cachedClient;
|
|
1103
|
+
}
|
|
1104
|
+
convertSchema(schema$1) {
|
|
1105
|
+
if (schema.isStandardSchemaWithJSON(schema$1)) {
|
|
1106
|
+
return schema.standardSchemaToJSONSchema(schema$1);
|
|
1107
|
+
}
|
|
1108
|
+
return schema$1?.jsonSchema || schema$1;
|
|
1109
|
+
}
|
|
1110
|
+
async fetchToolList() {
|
|
1111
|
+
if (this._cachedToolList) return this._cachedToolList;
|
|
1112
|
+
const client = await this.getClient();
|
|
1113
|
+
const tools = await client.tools();
|
|
1114
|
+
this._cachedToolList = {
|
|
1115
|
+
tools: Object.entries(tools).map(([toolName, tool]) => ({
|
|
1116
|
+
id: toolName,
|
|
1117
|
+
name: tool.id || toolName,
|
|
1118
|
+
description: tool.description,
|
|
1119
|
+
inputSchema: this.convertSchema(tool.inputSchema),
|
|
1120
|
+
outputSchema: this.convertSchema(tool.outputSchema),
|
|
1121
|
+
toolType: tool.mcp?.toolType,
|
|
1122
|
+
_meta: tool.mcp?._meta
|
|
1123
|
+
}))
|
|
1124
|
+
};
|
|
1125
|
+
this.convertedTools = tools;
|
|
1126
|
+
return this._cachedToolList;
|
|
1127
|
+
}
|
|
1128
|
+
// ---------- MCPServerBase abstract implementations ----------
|
|
1129
|
+
convertTools(_tools, _agents, _workflows) {
|
|
1130
|
+
return {};
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Returns the cached tool list synchronously, or triggers an async fetch.
|
|
1134
|
+
* The Studio API handlers are async and will auto-await a returned Promise.
|
|
1135
|
+
*/
|
|
1136
|
+
getToolListInfo() {
|
|
1137
|
+
if (this._cachedToolList) return this._cachedToolList;
|
|
1138
|
+
return this.fetchToolList();
|
|
1139
|
+
}
|
|
1140
|
+
getToolInfo(toolId) {
|
|
1141
|
+
if (this._cachedToolList) {
|
|
1142
|
+
return this._cachedToolList.tools.find((t) => t.id === toolId || t.name === toolId);
|
|
1143
|
+
}
|
|
1144
|
+
return this.fetchToolList().then((list) => list.tools.find((t) => t.id === toolId || t.name === toolId));
|
|
1145
|
+
}
|
|
1146
|
+
async executeTool(toolId, args, _executionContext) {
|
|
1147
|
+
const client = await this.getClient();
|
|
1148
|
+
const tools = await client.tools();
|
|
1149
|
+
const tool = tools[toolId];
|
|
1150
|
+
if (!tool) {
|
|
1151
|
+
throw new Error(`Tool '${toolId}' not found on remote MCP server '${this.name}'`);
|
|
1152
|
+
}
|
|
1153
|
+
if (!tool.execute) {
|
|
1154
|
+
throw new Error(`Tool '${toolId}' on remote MCP server '${this.name}' has no execute method`);
|
|
1155
|
+
}
|
|
1156
|
+
return tool.execute(args, _executionContext);
|
|
1157
|
+
}
|
|
1158
|
+
async listResources() {
|
|
1159
|
+
const client = await this.getClient();
|
|
1160
|
+
const resources = await client.resources.list();
|
|
1161
|
+
return {
|
|
1162
|
+
resources: resources.map((r) => ({
|
|
1163
|
+
uri: r.uri,
|
|
1164
|
+
name: r.name ?? r.uri,
|
|
1165
|
+
description: r.description,
|
|
1166
|
+
mimeType: r.mimeType,
|
|
1167
|
+
_meta: r._meta
|
|
1168
|
+
}))
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
async readResource(uri) {
|
|
1172
|
+
const client = await this.getClient();
|
|
1173
|
+
const result = await client.resources.read(uri);
|
|
1174
|
+
return {
|
|
1175
|
+
contents: (result.contents ?? []).map((c) => ({
|
|
1176
|
+
uri: c.uri ?? uri,
|
|
1177
|
+
...c.text !== void 0 ? { text: c.text } : {},
|
|
1178
|
+
...c.blob !== void 0 ? { blob: c.blob } : {}
|
|
1179
|
+
}))
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
// Transport methods — not applicable for client proxies
|
|
1183
|
+
async startStdio() {
|
|
1184
|
+
throw new Error("MCPClientServerProxy does not support stdio transport");
|
|
1185
|
+
}
|
|
1186
|
+
async startSSE(_options) {
|
|
1187
|
+
throw new Error("MCPClientServerProxy does not support SSE transport");
|
|
1188
|
+
}
|
|
1189
|
+
async startHonoSSE(_options) {
|
|
1190
|
+
throw new Error("MCPClientServerProxy does not support Hono SSE transport");
|
|
1191
|
+
}
|
|
1192
|
+
async startHTTP(_options) {
|
|
1193
|
+
throw new Error("MCPClientServerProxy does not support HTTP transport");
|
|
1194
|
+
}
|
|
1195
|
+
async close() {
|
|
1196
|
+
this.cachedClient = null;
|
|
1197
|
+
}
|
|
1198
|
+
getServerInfo() {
|
|
1199
|
+
return {
|
|
1200
|
+
id: this.id,
|
|
1201
|
+
name: this.name,
|
|
1202
|
+
description: this.description,
|
|
1203
|
+
version_detail: {
|
|
1204
|
+
version: this.version,
|
|
1205
|
+
release_date: this.releaseDate,
|
|
1206
|
+
is_latest: this.isLatest
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
getServerDetail() {
|
|
1211
|
+
return {
|
|
1212
|
+
...this.getServerInfo(),
|
|
1213
|
+
packages: this.packages ?? [],
|
|
1214
|
+
remotes: this.remotes ?? []
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1062
1217
|
};
|
|
1218
|
+
|
|
1219
|
+
// src/client/configuration.ts
|
|
1063
1220
|
var mcpClientInstances = /* @__PURE__ */ new Map();
|
|
1064
1221
|
var TOOL_DISCOVERY_MAX_ATTEMPTS = 2;
|
|
1065
1222
|
var MCPClient = class extends base.MastraBase {
|
|
@@ -1827,6 +1984,39 @@ To fix this you have three different options:
|
|
|
1827
1984
|
}
|
|
1828
1985
|
return { toolsets: connectedToolsets, errors };
|
|
1829
1986
|
}
|
|
1987
|
+
/**
|
|
1988
|
+
* Creates MCPServerBase-compatible proxy objects for each server connection
|
|
1989
|
+
* in this MCPClient. The returned record can be spread directly into
|
|
1990
|
+
* Mastra's `mcpServers` config so that external (non-Mastra) servers
|
|
1991
|
+
* appear in Studio alongside native MCPServer instances.
|
|
1992
|
+
*
|
|
1993
|
+
* @returns Record mapping server names to MCPServerBase proxy instances
|
|
1994
|
+
*
|
|
1995
|
+
* @example
|
|
1996
|
+
* ```typescript
|
|
1997
|
+
* const mcp = new MCPClient({
|
|
1998
|
+
* servers: {
|
|
1999
|
+
* trailhead: { command: 'npx', args: ['trailhead-server'] },
|
|
2000
|
+
* },
|
|
2001
|
+
* });
|
|
2002
|
+
*
|
|
2003
|
+
* const mastra = new Mastra({
|
|
2004
|
+
* mcpServers: {
|
|
2005
|
+
* ...mcp.toMCPServerProxies(),
|
|
2006
|
+
* },
|
|
2007
|
+
* });
|
|
2008
|
+
* ```
|
|
2009
|
+
*/
|
|
2010
|
+
toMCPServerProxies() {
|
|
2011
|
+
const proxies = {};
|
|
2012
|
+
for (const serverName of Object.keys(this.serverConfigs)) {
|
|
2013
|
+
proxies[serverName] = new MCPClientServerProxy(
|
|
2014
|
+
{ name: serverName, id: serverName },
|
|
2015
|
+
() => this.getConnectedClientForServer(serverName)
|
|
2016
|
+
);
|
|
2017
|
+
}
|
|
2018
|
+
return proxies;
|
|
2019
|
+
}
|
|
1830
2020
|
/**
|
|
1831
2021
|
* Gets current session IDs for all connected MCP clients using Streamable HTTP transport.
|
|
1832
2022
|
*
|
|
@@ -2684,19 +2874,28 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
2684
2874
|
*/
|
|
2685
2875
|
constructor(opts) {
|
|
2686
2876
|
super(opts);
|
|
2687
|
-
this.resourceOptions = opts.resources;
|
|
2877
|
+
this.resourceOptions = this.mergeAppResources(opts.resources, opts.appResources);
|
|
2688
2878
|
this.promptOptions = opts.prompts;
|
|
2689
2879
|
this.jsonSchemaValidator = opts.jsonSchemaValidator;
|
|
2690
2880
|
const capabilities = {
|
|
2691
2881
|
tools: {},
|
|
2692
2882
|
logging: { enabled: true }
|
|
2693
2883
|
};
|
|
2694
|
-
if (
|
|
2884
|
+
if (this.resourceOptions) {
|
|
2695
2885
|
capabilities.resources = { subscribe: true, listChanged: true };
|
|
2696
2886
|
}
|
|
2697
2887
|
if (opts.prompts) {
|
|
2698
2888
|
capabilities.prompts = { listChanged: true };
|
|
2699
2889
|
}
|
|
2890
|
+
const hasUiTools = Object.values(this.convertedTools).some(
|
|
2891
|
+
(tool) => tool.mcp?._meta?.ui?.resourceUri
|
|
2892
|
+
);
|
|
2893
|
+
if (hasUiTools || opts.appResources) {
|
|
2894
|
+
capabilities.extensions = {
|
|
2895
|
+
...capabilities.extensions,
|
|
2896
|
+
"io.modelcontextprotocol/ui": {}
|
|
2897
|
+
};
|
|
2898
|
+
}
|
|
2700
2899
|
this.server = new index_js$1.Server(
|
|
2701
2900
|
{
|
|
2702
2901
|
name: this.name,
|
|
@@ -2792,6 +2991,81 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
2792
2991
|
req.on("error", reject);
|
|
2793
2992
|
});
|
|
2794
2993
|
}
|
|
2994
|
+
/**
|
|
2995
|
+
* Merges appResources into the resource system alongside any user-provided resources.
|
|
2996
|
+
*
|
|
2997
|
+
* App resources are auto-registered as `ui://` resources with the MCP Apps MIME type.
|
|
2998
|
+
* If the user also provides a `resources` config, the two are merged — user callbacks
|
|
2999
|
+
* take precedence for overlapping URIs.
|
|
3000
|
+
*/
|
|
3001
|
+
mergeAppResources(userResources, appResources) {
|
|
3002
|
+
if (!appResources || Object.keys(appResources).length === 0) {
|
|
3003
|
+
return userResources;
|
|
3004
|
+
}
|
|
3005
|
+
const resolvedAppResources = /* @__PURE__ */ new Map();
|
|
3006
|
+
for (const [uri, appResource] of Object.entries(appResources)) {
|
|
3007
|
+
let html;
|
|
3008
|
+
if (appResource.html) {
|
|
3009
|
+
html = appResource.html;
|
|
3010
|
+
} else if (appResource.htmlPath) {
|
|
3011
|
+
html = fs.readFileSync(appResource.htmlPath, "utf-8");
|
|
3012
|
+
} else {
|
|
3013
|
+
this.logger.warn(`App resource '${uri}' has neither html nor htmlPath \u2014 skipping`);
|
|
3014
|
+
continue;
|
|
3015
|
+
}
|
|
3016
|
+
const resource = {
|
|
3017
|
+
uri,
|
|
3018
|
+
name: appResource.name,
|
|
3019
|
+
...appResource.description ? { description: appResource.description } : {},
|
|
3020
|
+
mimeType: extApps.RESOURCE_MIME_TYPE,
|
|
3021
|
+
...appResource.meta ? { _meta: { ui: appResource.meta } } : {}
|
|
3022
|
+
};
|
|
3023
|
+
resolvedAppResources.set(uri, { resource, html });
|
|
3024
|
+
}
|
|
3025
|
+
if (resolvedAppResources.size === 0) {
|
|
3026
|
+
return userResources;
|
|
3027
|
+
}
|
|
3028
|
+
const appListResources = async () => {
|
|
3029
|
+
return Array.from(resolvedAppResources.values()).map((r) => r.resource);
|
|
3030
|
+
};
|
|
3031
|
+
const appGetResourceContent = async ({ uri }) => {
|
|
3032
|
+
const appRes = resolvedAppResources.get(uri);
|
|
3033
|
+
if (appRes) {
|
|
3034
|
+
return { text: appRes.html };
|
|
3035
|
+
}
|
|
3036
|
+
throw new Error(`App resource not found: ${uri}`);
|
|
3037
|
+
};
|
|
3038
|
+
if (!userResources) {
|
|
3039
|
+
return {
|
|
3040
|
+
listResources: appListResources,
|
|
3041
|
+
getResourceContent: appGetResourceContent
|
|
3042
|
+
};
|
|
3043
|
+
}
|
|
3044
|
+
return {
|
|
3045
|
+
listResources: async ({ extra }) => {
|
|
3046
|
+
const userResourceList = await userResources.listResources({ extra });
|
|
3047
|
+
const appResourceList = await appListResources();
|
|
3048
|
+
const userUris = new Set(userResourceList.map((r) => r.uri));
|
|
3049
|
+
const nonConflicting = appResourceList.filter((r) => !userUris.has(r.uri));
|
|
3050
|
+
return [...userResourceList, ...nonConflicting];
|
|
3051
|
+
},
|
|
3052
|
+
getResourceContent: async ({ uri, extra }) => {
|
|
3053
|
+
const appRes = resolvedAppResources.get(uri);
|
|
3054
|
+
if (appRes) {
|
|
3055
|
+
try {
|
|
3056
|
+
const userResourceList = await userResources.listResources({ extra });
|
|
3057
|
+
if (userResourceList.some((r) => r.uri === uri)) {
|
|
3058
|
+
return userResources.getResourceContent({ uri, extra });
|
|
3059
|
+
}
|
|
3060
|
+
} catch {
|
|
3061
|
+
}
|
|
3062
|
+
return { text: appRes.html };
|
|
3063
|
+
}
|
|
3064
|
+
return userResources.getResourceContent({ uri, extra });
|
|
3065
|
+
},
|
|
3066
|
+
...userResources.resourceTemplates ? { resourceTemplates: userResources.resourceTemplates } : {}
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
2795
3069
|
/**
|
|
2796
3070
|
* Creates a new Server instance configured with all handlers for HTTP sessions.
|
|
2797
3071
|
* Each HTTP client connection gets its own Server instance to avoid routing conflicts.
|
|
@@ -2807,6 +3081,18 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
2807
3081
|
if (this.promptOptions) {
|
|
2808
3082
|
capabilities.prompts = { listChanged: true };
|
|
2809
3083
|
}
|
|
3084
|
+
const hasUiTools = Object.values(this.convertedTools).some(
|
|
3085
|
+
(tool) => tool.mcp?._meta?.ui?.resourceUri
|
|
3086
|
+
);
|
|
3087
|
+
if (hasUiTools || this.resourceOptions) {
|
|
3088
|
+
const hasUiResources = this.definedResources?.some((r) => r.uri.startsWith("ui://"));
|
|
3089
|
+
if (hasUiTools || hasUiResources) {
|
|
3090
|
+
capabilities.extensions = {
|
|
3091
|
+
...capabilities.extensions,
|
|
3092
|
+
"io.modelcontextprotocol/ui": {}
|
|
3093
|
+
};
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
2810
3096
|
const serverInstance = new index_js$1.Server(
|
|
2811
3097
|
{
|
|
2812
3098
|
name: this.name,
|
|
@@ -2844,7 +3130,15 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
2844
3130
|
}
|
|
2845
3131
|
const toolMeta = withMastraToolStrictMeta(tool.mcp?._meta, tool.strict);
|
|
2846
3132
|
if (toolMeta) {
|
|
2847
|
-
|
|
3133
|
+
const uiMeta = toolMeta.ui;
|
|
3134
|
+
const legacyUri = toolMeta[extApps.RESOURCE_URI_META_KEY];
|
|
3135
|
+
if (uiMeta?.resourceUri && !legacyUri) {
|
|
3136
|
+
toolSpec._meta = { ...toolMeta, [extApps.RESOURCE_URI_META_KEY]: uiMeta.resourceUri };
|
|
3137
|
+
} else if (legacyUri && !uiMeta?.resourceUri) {
|
|
3138
|
+
toolSpec._meta = { ...toolMeta, ui: { ...toolMeta.ui ?? {}, resourceUri: legacyUri } };
|
|
3139
|
+
} else {
|
|
3140
|
+
toolSpec._meta = toolMeta;
|
|
3141
|
+
}
|
|
2848
3142
|
}
|
|
2849
3143
|
return toolSpec;
|
|
2850
3144
|
})
|
|
@@ -4337,6 +4631,53 @@ Provided arguments: ${JSON.stringify(args, null, 2)}`,
|
|
|
4337
4631
|
throw mastraError;
|
|
4338
4632
|
}
|
|
4339
4633
|
}
|
|
4634
|
+
/**
|
|
4635
|
+
* Reads the content of a resource by URI.
|
|
4636
|
+
*
|
|
4637
|
+
* Used by the Studio API to proxy `ui://` resource reads for MCP Apps rendering.
|
|
4638
|
+
*
|
|
4639
|
+
* @param uri - The resource URI to read (e.g. `ui://weather/dashboard`)
|
|
4640
|
+
* @returns Promise resolving to the resource content
|
|
4641
|
+
*/
|
|
4642
|
+
async readResource(uri) {
|
|
4643
|
+
if (!this.resourceOptions?.getResourceContent) {
|
|
4644
|
+
throw new error.MastraError({
|
|
4645
|
+
id: "MCP_SERVER_RESOURCES_NOT_CONFIGURED",
|
|
4646
|
+
domain: error.ErrorDomain.MCP,
|
|
4647
|
+
category: error.ErrorCategory.USER,
|
|
4648
|
+
details: { uri }
|
|
4649
|
+
});
|
|
4650
|
+
}
|
|
4651
|
+
const extra = {};
|
|
4652
|
+
const result = await this.resourceOptions.getResourceContent({ uri, extra });
|
|
4653
|
+
const contents = Array.isArray(result) ? result : [result];
|
|
4654
|
+
return {
|
|
4655
|
+
contents: contents.map((c) => ({
|
|
4656
|
+
uri,
|
|
4657
|
+
..."text" in c && c.text !== void 0 ? { text: c.text } : {},
|
|
4658
|
+
..."blob" in c && c.blob !== void 0 ? { blob: c.blob } : {}
|
|
4659
|
+
}))
|
|
4660
|
+
};
|
|
4661
|
+
}
|
|
4662
|
+
/**
|
|
4663
|
+
* Lists all resources available on this MCP server.
|
|
4664
|
+
*
|
|
4665
|
+
* Used by the Studio API to discover `ui://` resources for MCP Apps.
|
|
4666
|
+
*
|
|
4667
|
+
* @returns Promise resolving to the list of resources
|
|
4668
|
+
*/
|
|
4669
|
+
async listResources() {
|
|
4670
|
+
if (!this.resourceOptions?.listResources) {
|
|
4671
|
+
return { resources: [] };
|
|
4672
|
+
}
|
|
4673
|
+
const extra = {};
|
|
4674
|
+
if (this.definedResources) {
|
|
4675
|
+
return { resources: this.definedResources };
|
|
4676
|
+
}
|
|
4677
|
+
const resources = await this.resourceOptions.listResources({ extra });
|
|
4678
|
+
this.definedResources = resources;
|
|
4679
|
+
return { resources };
|
|
4680
|
+
}
|
|
4340
4681
|
};
|
|
4341
4682
|
function escapeHeaderValue(value) {
|
|
4342
4683
|
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
@@ -4597,6 +4938,7 @@ Object.defineProperty(exports, "startAuthorization", {
|
|
|
4597
4938
|
exports.InMemoryOAuthStorage = InMemoryOAuthStorage;
|
|
4598
4939
|
exports.InternalMastraMCPClient = InternalMastraMCPClient;
|
|
4599
4940
|
exports.MCPClient = MCPClient;
|
|
4941
|
+
exports.MCPClientServerProxy = MCPClientServerProxy;
|
|
4600
4942
|
exports.MCPOAuthClientProvider = MCPOAuthClientProvider;
|
|
4601
4943
|
exports.MCPServer = MCPServer;
|
|
4602
4944
|
exports.createIntrospectionValidator = createIntrospectionValidator;
|