@dbx-tools/appkit-mastra 0.1.58 → 0.1.67
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/README.md +41 -1
- package/dist/index.d.ts +1229 -17
- package/dist/index.js +3348 -19
- package/package.json +17 -28
- package/src/agents.ts +7 -1
- package/src/config.ts +64 -0
- package/src/genie.ts +9 -21
- package/{index.ts → src/index.ts} +7 -9
- package/src/mcp.ts +89 -0
- package/src/model.ts +9 -3
- package/src/plugin.ts +51 -8
- package/src/serving.ts +5 -9
- package/src/statement.ts +8 -36
- package/dist/src/agents.d.ts +0 -316
- package/dist/src/agents.js +0 -470
- package/dist/src/chart.d.ts +0 -153
- package/dist/src/chart.js +0 -570
- package/dist/src/config.d.ts +0 -295
- package/dist/src/config.js +0 -55
- package/dist/src/genie.d.ts +0 -161
- package/dist/src/genie.js +0 -973
- package/dist/src/history.d.ts +0 -95
- package/dist/src/history.js +0 -278
- package/dist/src/memory.d.ts +0 -109
- package/dist/src/memory.js +0 -253
- package/dist/src/model.d.ts +0 -52
- package/dist/src/model.js +0 -198
- package/dist/src/observability.d.ts +0 -64
- package/dist/src/observability.js +0 -83
- package/dist/src/plugin.d.ts +0 -177
- package/dist/src/plugin.js +0 -554
- package/dist/src/processor.d.ts +0 -8
- package/dist/src/processor.js +0 -40
- package/dist/src/processors/strip-stale-charts.d.ts +0 -32
- package/dist/src/processors/strip-stale-charts.js +0 -98
- package/dist/src/server.d.ts +0 -51
- package/dist/src/server.js +0 -152
- package/dist/src/serving.d.ts +0 -65
- package/dist/src/serving.js +0 -79
- package/dist/src/statement.d.ts +0 -66
- package/dist/src/statement.js +0 -111
- package/dist/src/writer.d.ts +0 -23
- package/dist/src/writer.js +0 -37
- package/dist/tsconfig.build.tsbuildinfo +0 -1
package/package.json
CHANGED
|
@@ -1,26 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
"main": "./dist/index.js",
|
|
3
|
-
"types": "./dist/index.d.ts",
|
|
4
|
-
"exports": {
|
|
5
|
-
".": {
|
|
6
|
-
"source": "./index.ts",
|
|
7
|
-
"types": "./dist/index.d.ts",
|
|
8
|
-
"default": "./dist/index.js"
|
|
9
|
-
}
|
|
10
|
-
},
|
|
11
2
|
"name": "@dbx-tools/appkit-mastra",
|
|
12
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.67",
|
|
13
4
|
"dependencies": {
|
|
14
5
|
"@databricks/sdk-experimental": "^0.17",
|
|
15
|
-
"@dbx-tools/appkit-mastra-shared": "0.1.
|
|
16
|
-
"@dbx-tools/genie": "0.1.
|
|
17
|
-
"@dbx-tools/genie-shared": "0.1.
|
|
18
|
-
"@dbx-tools/model": "0.1.
|
|
19
|
-
"@dbx-tools/shared": "0.1.
|
|
6
|
+
"@dbx-tools/appkit-mastra-shared": "0.1.67",
|
|
7
|
+
"@dbx-tools/genie": "0.1.67",
|
|
8
|
+
"@dbx-tools/genie-shared": "0.1.67",
|
|
9
|
+
"@dbx-tools/model": "0.1.67",
|
|
10
|
+
"@dbx-tools/shared": "0.1.67",
|
|
20
11
|
"@mastra/ai-sdk": "^1",
|
|
21
12
|
"@mastra/core": "^1",
|
|
22
13
|
"@mastra/express": "^1",
|
|
23
14
|
"@mastra/fastembed": "^1",
|
|
15
|
+
"@mastra/mcp": "^1",
|
|
24
16
|
"@mastra/memory": "^1",
|
|
25
17
|
"@mastra/observability": "^1",
|
|
26
18
|
"@mastra/otel-bridge": "^1",
|
|
@@ -28,26 +20,23 @@
|
|
|
28
20
|
"pg": "^8.20.0",
|
|
29
21
|
"zod": "^4.3.6"
|
|
30
22
|
},
|
|
31
|
-
"
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"source": "./src/index.ts",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
32
30
|
"peerDependencies": {
|
|
33
31
|
"@databricks/appkit": "^0.41",
|
|
34
32
|
"express": "^5"
|
|
35
33
|
},
|
|
36
34
|
"type": "module",
|
|
35
|
+
"main": "./dist/index.js",
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
37
|
"files": [
|
|
38
|
-
"bin",
|
|
39
38
|
"dist",
|
|
40
|
-
"index*.ts",
|
|
41
39
|
"src"
|
|
42
40
|
],
|
|
43
|
-
"license": "Apache-2.0"
|
|
44
|
-
"homepage": "https://github.com/reggie-db/dbx-tools-js#readme",
|
|
45
|
-
"bugs": {
|
|
46
|
-
"url": "https://github.com/reggie-db/dbx-tools-js/issues"
|
|
47
|
-
},
|
|
48
|
-
"repository": {
|
|
49
|
-
"type": "git",
|
|
50
|
-
"url": "git+https://github.com/reggie-db/dbx-tools-js.git",
|
|
51
|
-
"directory": "packages/appkit-mastra"
|
|
52
|
-
}
|
|
41
|
+
"license": "Apache-2.0"
|
|
53
42
|
}
|
package/src/agents.ts
CHANGED
|
@@ -319,6 +319,12 @@ export type MastraMemoryConfigOverride = DistributiveOmit<PgVectorConfig, "id">
|
|
|
319
319
|
export interface BuiltAgents {
|
|
320
320
|
agents: Record<string, Agent>;
|
|
321
321
|
defaultAgentId: string;
|
|
322
|
+
/**
|
|
323
|
+
* Ambient tools shared across every agent (the built-in system tools
|
|
324
|
+
* spread with `config.tools`). Surfaced so the optional MCP server
|
|
325
|
+
* can re-expose them when {@link MastraMcpConfig.tools} is enabled.
|
|
326
|
+
*/
|
|
327
|
+
ambientTools: MastraTools;
|
|
322
328
|
}
|
|
323
329
|
|
|
324
330
|
/** Fallback agent id used when `config.agents` is omitted entirely. */
|
|
@@ -477,7 +483,7 @@ export async function buildAgents(opts: {
|
|
|
477
483
|
}
|
|
478
484
|
|
|
479
485
|
log.info("agents ready", { ids, defaultAgentId });
|
|
480
|
-
return { agents, defaultAgentId };
|
|
486
|
+
return { agents, defaultAgentId, ambientTools };
|
|
481
487
|
}
|
|
482
488
|
|
|
483
489
|
/**
|
package/src/config.ts
CHANGED
|
@@ -80,6 +80,45 @@ export type MastraMemoryConfig = PgVectorConfig & {
|
|
|
80
80
|
id?: string;
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Fine-grained control for the optional MCP server exposure
|
|
85
|
+
* ({@link MastraPluginConfig.mcp}). Every field is optional; the object
|
|
86
|
+
* form only needs to set what differs from the defaults.
|
|
87
|
+
*/
|
|
88
|
+
export interface MastraMcpConfig {
|
|
89
|
+
/**
|
|
90
|
+
* Server id used in the route path (`/mcp/<serverId>/...`) and as the
|
|
91
|
+
* MCP registry id. Defaults to the plugin's registered name.
|
|
92
|
+
*/
|
|
93
|
+
serverId?: string;
|
|
94
|
+
/** Display name advertised over MCP. Defaults to `"<displayName> MCP"`. */
|
|
95
|
+
name?: string;
|
|
96
|
+
/** Semantic version advertised over MCP. Defaults to `"1.0.0"`. */
|
|
97
|
+
version?: string;
|
|
98
|
+
/** Optional human-readable description advertised over MCP. */
|
|
99
|
+
description?: string;
|
|
100
|
+
/**
|
|
101
|
+
* Expose every registered agent as an `ask_<agentId>` MCP tool.
|
|
102
|
+
* Defaults to `true` - this is the "leverage the Mastra agents over
|
|
103
|
+
* MCP" behavior most callers want.
|
|
104
|
+
*/
|
|
105
|
+
agents?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Also expose the plugin's ambient tools (the built-in `render_data`
|
|
108
|
+
* plus anything in `config.tools`) as MCP tools. Defaults to `false`:
|
|
109
|
+
* the ambient tools assume an in-process chat turn (they publish
|
|
110
|
+
* writer events the chat UI consumes), so they aren't useful to a
|
|
111
|
+
* standalone MCP client. Turn this on only when those tools are safe
|
|
112
|
+
* to call out-of-band.
|
|
113
|
+
*/
|
|
114
|
+
tools?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Extra tools to expose over MCP beyond the agent / ambient sets.
|
|
117
|
+
* Use this for tools written specifically for MCP consumers.
|
|
118
|
+
*/
|
|
119
|
+
extraTools?: MastraTools;
|
|
120
|
+
}
|
|
121
|
+
|
|
83
122
|
/** Configuration accepted by the Mastra AppKit plugin. */
|
|
84
123
|
export interface MastraPluginConfig extends BasePluginConfig {
|
|
85
124
|
/** Mastra OpenAI-compatible provider id. Defaults to `"databricks"`. */
|
|
@@ -317,4 +356,29 @@ export interface MastraPluginConfig extends BasePluginConfig {
|
|
|
317
356
|
* to drill deep into a dataset within a single turn.
|
|
318
357
|
*/
|
|
319
358
|
agentMaxSteps?: number;
|
|
359
|
+
/**
|
|
360
|
+
* Expose the plugin's agents (and optionally tools) as a Mastra MCP
|
|
361
|
+
* server so external MCP clients - Claude Desktop, Cursor, the Mastra
|
|
362
|
+
* playground, or another agent - can call them over the standard MCP
|
|
363
|
+
* transports. Disabled by default.
|
|
364
|
+
*
|
|
365
|
+
* - `undefined` / `false`: no MCP endpoints.
|
|
366
|
+
* - `true`: expose every registered agent as an `ask_<agentId>` MCP
|
|
367
|
+
* tool under a server whose id is the plugin name.
|
|
368
|
+
* - {@link MastraMcpConfig}: fine-grained control over the server id,
|
|
369
|
+
* advertised metadata, and which agents / tools are exposed.
|
|
370
|
+
*
|
|
371
|
+
* When enabled, the stock Mastra MCP routes mount under the plugin's
|
|
372
|
+
* base path (no bespoke route is added - the server is handed to the
|
|
373
|
+
* `Mastra` instance via `mcpServers`, which `@mastra/express` serves):
|
|
374
|
+
*
|
|
375
|
+
* - Streamable HTTP: `POST /api/<plugin>/mcp/<serverId>/mcp`
|
|
376
|
+
* - SSE (legacy): `GET /api/<plugin>/mcp/<serverId>/sse`
|
|
377
|
+
* `POST /api/<plugin>/mcp/<serverId>/messages`
|
|
378
|
+
*
|
|
379
|
+
* Requests run under the same AppKit OBO scope as the chat routes, so
|
|
380
|
+
* an agent invoked over MCP resolves its model and tools as the
|
|
381
|
+
* calling user.
|
|
382
|
+
*/
|
|
383
|
+
mcp?: boolean | MastraMcpConfig;
|
|
320
384
|
}
|
package/src/genie.ts
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
import { CacheManager, genie } from "@databricks/appkit";
|
|
38
|
-
import {
|
|
38
|
+
import { WorkspaceClient } from "@databricks/sdk-experimental";
|
|
39
39
|
import {
|
|
40
40
|
ChartSchema,
|
|
41
41
|
type MastraWriter,
|
|
@@ -43,7 +43,13 @@ import {
|
|
|
43
43
|
} from "@dbx-tools/appkit-mastra-shared";
|
|
44
44
|
import { genieEventChat, genieSampleQuestions, getGenieSpace } from "@dbx-tools/genie";
|
|
45
45
|
import { GenieMessageSchema, type GenieMessage } from "@dbx-tools/genie-shared";
|
|
46
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
apiUtils,
|
|
48
|
+
appkitUtils,
|
|
49
|
+
commonUtils,
|
|
50
|
+
logUtils,
|
|
51
|
+
stringUtils,
|
|
52
|
+
} from "@dbx-tools/shared";
|
|
47
53
|
import type { RequestContext } from "@mastra/core/request-context";
|
|
48
54
|
import { MASTRA_THREAD_ID_KEY } from "@mastra/core/request-context";
|
|
49
55
|
import { createTool } from "@mastra/core/tools";
|
|
@@ -302,24 +308,6 @@ async function ensureConversationSeeded(
|
|
|
302
308
|
if (cached) writeContextConversationId(requestContext, spaceId, cached);
|
|
303
309
|
}
|
|
304
310
|
|
|
305
|
-
/**
|
|
306
|
-
* True when `err` is the SDK error Genie returns for a
|
|
307
|
-
* conversation id that no longer exists (deleted, expired upstream,
|
|
308
|
-
* or referenced from the wrong space). Matches the typed
|
|
309
|
-
* {@link ApiError} 404 / `RESOURCE_DOES_NOT_EXIST` shape first, then
|
|
310
|
-
* falls back to the lower-level {@link HttpError} 404, then to a
|
|
311
|
-
* loose message sniff for SDK shapes we haven't catalogued.
|
|
312
|
-
*/
|
|
313
|
-
function isConversationGoneError(err: unknown): boolean {
|
|
314
|
-
if (err instanceof ApiError) {
|
|
315
|
-
if (err.statusCode === 404) return true;
|
|
316
|
-
if (err.errorCode === "RESOURCE_DOES_NOT_EXIST") return true;
|
|
317
|
-
}
|
|
318
|
-
if (err instanceof HttpError && err.code === 404) return true;
|
|
319
|
-
if (err instanceof Error && /does not exist/i.test(err.message)) return true;
|
|
320
|
-
return false;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
311
|
/* ------------------------ prepare_chart input ------------------------ */
|
|
324
312
|
|
|
325
313
|
/**
|
|
@@ -506,7 +494,7 @@ function buildAskGenieTool(opts: { spaceId: string; alias: string; hint?: string
|
|
|
506
494
|
// and try once more. Only retry when we *had* a seeded id -
|
|
507
495
|
// a fresh call that 404s shouldn't loop.
|
|
508
496
|
const seeded = readContextConversationId(requestContext, spaceId);
|
|
509
|
-
if (seeded &&
|
|
497
|
+
if (seeded && apiUtils.isNotFoundError(err)) {
|
|
510
498
|
log.warn("conversation-cache:stale, resetting", {
|
|
511
499
|
spaceId,
|
|
512
500
|
conversationId: seeded,
|
|
@@ -12,16 +12,14 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export * from "@dbx-tools/appkit-mastra-shared";
|
|
14
14
|
export * from "@dbx-tools/model";
|
|
15
|
-
export * from "./
|
|
16
|
-
export * from "./
|
|
17
|
-
export * from "./
|
|
18
|
-
export * from "./
|
|
19
|
-
export * from "./
|
|
15
|
+
export * from "./agents.js";
|
|
16
|
+
export * from "./chart.js";
|
|
17
|
+
export * from "./config.js";
|
|
18
|
+
export * from "./genie.js";
|
|
19
|
+
export * from "./mcp.js";
|
|
20
|
+
export * from "./plugin.js";
|
|
20
21
|
export {
|
|
21
22
|
MASTRA_MODEL_OVERRIDE_KEY,
|
|
22
|
-
MODEL_OVERRIDE_BODY_FIELDS,
|
|
23
|
-
MODEL_OVERRIDE_HEADER,
|
|
24
|
-
MODEL_OVERRIDE_QUERY,
|
|
25
23
|
extractModelOverride,
|
|
26
24
|
type ModelOverrideRequest,
|
|
27
|
-
} from "./
|
|
25
|
+
} from "./serving.js";
|
package/src/mcp.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional Mastra MCP server exposure for the AppKit Mastra plugin.
|
|
3
|
+
*
|
|
4
|
+
* Turns the plugin's registered agents (and, opt-in, its ambient tools)
|
|
5
|
+
* into a Mastra `MCPServer` so external MCP clients - Claude Desktop,
|
|
6
|
+
* Cursor, the Mastra playground, or another agent - can call them over
|
|
7
|
+
* the standard MCP transports. The resulting server is handed to the
|
|
8
|
+
* `Mastra` instance via `mcpServers`, which makes `@mastra/express`
|
|
9
|
+
* serve the stock MCP routes under the plugin's base path; the plugin
|
|
10
|
+
* never registers a bespoke MCP route of its own.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { Agent } from "@mastra/core/agent";
|
|
14
|
+
import { MCPServer } from "@mastra/mcp";
|
|
15
|
+
|
|
16
|
+
import type { MastraTools } from "./agents.js";
|
|
17
|
+
import type { MastraMcpConfig, MastraPluginConfig } from "./config.js";
|
|
18
|
+
|
|
19
|
+
/** MCP server version advertised when the caller doesn't pin one. */
|
|
20
|
+
const DEFAULT_MCP_VERSION = "1.0.0";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A built MCP server plus the request paths it answers on, relative to
|
|
24
|
+
* the plugin's base path (`/api/<plugin>`). The paths are informational
|
|
25
|
+
* (logging / `exports().getMcp()`); the routes themselves are mounted
|
|
26
|
+
* by `@mastra/express` off the `Mastra` instance's `mcpServers`.
|
|
27
|
+
*/
|
|
28
|
+
export interface ResolvedMcp {
|
|
29
|
+
/** Registry id, used in the route path (`/mcp/<serverId>/...`). */
|
|
30
|
+
serverId: string;
|
|
31
|
+
/** The Mastra MCP server to hand to `new Mastra({ mcpServers })`. */
|
|
32
|
+
server: MCPServer;
|
|
33
|
+
/** Streamable-HTTP transport path, relative to the plugin base path. */
|
|
34
|
+
httpPath: string;
|
|
35
|
+
/** SSE transport path, relative to the plugin base path. */
|
|
36
|
+
ssePath: string;
|
|
37
|
+
/** SSE message path, relative to the plugin base path. */
|
|
38
|
+
messagePath: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build the plugin's MCP server, or `null` when `config.mcp` is falsy
|
|
43
|
+
* (the default - no MCP endpoints).
|
|
44
|
+
*
|
|
45
|
+
* `true` exposes every registered agent as an `ask_<agentId>` MCP tool
|
|
46
|
+
* under a server id equal to the plugin name. The object form
|
|
47
|
+
* ({@link MastraMcpConfig}) tunes the id / advertised metadata and can
|
|
48
|
+
* additionally expose the plugin's ambient tools or a set of extra
|
|
49
|
+
* MCP-only tools.
|
|
50
|
+
*/
|
|
51
|
+
export function buildMcpServer(opts: {
|
|
52
|
+
config: MastraPluginConfig;
|
|
53
|
+
pluginName: string;
|
|
54
|
+
displayName: string;
|
|
55
|
+
agents: Record<string, Agent>;
|
|
56
|
+
ambientTools: MastraTools;
|
|
57
|
+
}): ResolvedMcp | null {
|
|
58
|
+
const { config, pluginName, displayName, agents, ambientTools } = opts;
|
|
59
|
+
if (!config.mcp) return null;
|
|
60
|
+
const settings: MastraMcpConfig = config.mcp === true ? {} : config.mcp;
|
|
61
|
+
|
|
62
|
+
const serverId = settings.serverId ?? pluginName;
|
|
63
|
+
const exposeAgents = settings.agents !== false;
|
|
64
|
+
const exposeTools = settings.tools === true;
|
|
65
|
+
|
|
66
|
+
// MCPServerConfig.tools is required, so always pass a record (empty
|
|
67
|
+
// when the caller exposes only agents).
|
|
68
|
+
const tools: MastraTools = {
|
|
69
|
+
...(exposeTools ? ambientTools : {}),
|
|
70
|
+
...(settings.extraTools ?? {}),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const server = new MCPServer({
|
|
74
|
+
id: serverId,
|
|
75
|
+
name: settings.name ?? `${displayName} MCP`,
|
|
76
|
+
version: settings.version ?? DEFAULT_MCP_VERSION,
|
|
77
|
+
...(settings.description ? { description: settings.description } : {}),
|
|
78
|
+
...(exposeAgents ? { agents } : {}),
|
|
79
|
+
tools,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
serverId,
|
|
84
|
+
server,
|
|
85
|
+
httpPath: `/mcp/${serverId}/mcp`,
|
|
86
|
+
ssePath: `/mcp/${serverId}/sse`,
|
|
87
|
+
messagePath: `/mcp/${serverId}/messages`,
|
|
88
|
+
};
|
|
89
|
+
}
|
package/src/model.ts
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* propagate so callers see the real SDK message.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
+
import { getExecutionContext } from "@databricks/appkit";
|
|
24
25
|
import { type ModelClass, parseModelClass, selectModel } from "@dbx-tools/model";
|
|
25
26
|
import { commonUtils, logUtils, netUtils, stringUtils } from "@dbx-tools/shared";
|
|
26
27
|
import type { MastraModelConfig } from "@mastra/core/llm";
|
|
@@ -68,8 +69,13 @@ export async function buildModel(
|
|
|
68
69
|
overrides: BuildModelOverrides = {},
|
|
69
70
|
): Promise<MastraModelConfig> {
|
|
70
71
|
void setupFetchInterceptor();
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
// The chat path stamps the AppKit user on the request context via
|
|
73
|
+
// `MastraServer`. The MCP transport routes don't thread that context
|
|
74
|
+
// into tool execution, so fall back to the ambient execution context
|
|
75
|
+
// (the active OBO scope, or the service principal) when it's absent.
|
|
76
|
+
const user = requestContext.get(MASTRA_USER_KEY) as User | undefined;
|
|
77
|
+
const executionContext = user?.executionContext ?? getExecutionContext();
|
|
78
|
+
const clientConfig = executionContext.client.config;
|
|
73
79
|
const host = (await clientConfig.getHost()).toString();
|
|
74
80
|
const headers = new Headers();
|
|
75
81
|
await clientConfig.authenticate(headers);
|
|
@@ -97,7 +103,7 @@ export async function buildModel(
|
|
|
97
103
|
const explicit = requestedClass === null ? requested : undefined;
|
|
98
104
|
const modelClass = requestedClass ?? overrides.modelClass;
|
|
99
105
|
|
|
100
|
-
const { modelId, source } = await selectModel(
|
|
106
|
+
const { modelId, source } = await selectModel(executionContext.client, host, {
|
|
101
107
|
...(explicit !== undefined ? { explicit } : {}),
|
|
102
108
|
fuzzy: serving.fuzzy,
|
|
103
109
|
threshold: serving.threshold,
|
package/src/plugin.ts
CHANGED
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
* (load + clear thread history), `/models`, `/suggestions`, and the
|
|
27
27
|
* generic `/embed/:type/:id` resolver for inline chart / data
|
|
28
28
|
* markers.
|
|
29
|
+
* - MCP: opt in with `config.mcp` to expose the agents (and optionally
|
|
30
|
+
* tools) as a Mastra `MCPServer`. It is registered on the `Mastra`
|
|
31
|
+
* instance via `mcpServers`, so `@mastra/express` serves the stock
|
|
32
|
+
* MCP transport routes (`/mcp/<serverId>/...`) under the mount. See
|
|
33
|
+
* `./mcp.js`.
|
|
29
34
|
*/
|
|
30
35
|
|
|
31
36
|
import {
|
|
@@ -38,7 +43,7 @@ import {
|
|
|
38
43
|
type PluginManifest,
|
|
39
44
|
type ResourceRequirement,
|
|
40
45
|
} from "@databricks/appkit";
|
|
41
|
-
import { appkitUtils, commonUtils, logUtils } from "@dbx-tools/shared";
|
|
46
|
+
import { apiUtils, appkitUtils, commonUtils, logUtils } from "@dbx-tools/shared";
|
|
42
47
|
import type { Agent } from "@mastra/core/agent";
|
|
43
48
|
import { Mastra } from "@mastra/core/mastra";
|
|
44
49
|
import express from "express";
|
|
@@ -59,6 +64,7 @@ import { fetchChart } from "./chart.js";
|
|
|
59
64
|
import type { MastraPluginConfig } from "./config.js";
|
|
60
65
|
import { collectSpaceSuggestions, resolveGenieSpaces } from "./genie.js";
|
|
61
66
|
import { historyRoute } from "./history.js";
|
|
67
|
+
import { buildMcpServer, type ResolvedMcp } from "./mcp.js";
|
|
62
68
|
import {
|
|
63
69
|
createMemoryBuilder,
|
|
64
70
|
createServicePrincipalPool,
|
|
@@ -67,11 +73,7 @@ import {
|
|
|
67
73
|
import { buildObservability } from "./observability.js";
|
|
68
74
|
import { attachRoutePatchMiddleware, MastraServer } from "./server.js";
|
|
69
75
|
import { resolveServingConfig } from "./serving.js";
|
|
70
|
-
import {
|
|
71
|
-
fetchStatementData,
|
|
72
|
-
isStatementNotFoundError,
|
|
73
|
-
STATEMENT_ROW_CAP,
|
|
74
|
-
} from "./statement.js";
|
|
76
|
+
import { fetchStatementData, STATEMENT_ROW_CAP } from "./statement.js";
|
|
75
77
|
|
|
76
78
|
const GENIE_MANIFEST = appkitUtils.data(genie).plugin.manifest;
|
|
77
79
|
const LAKEBASE_MANIFEST = appkitUtils.data(lakebase).plugin.manifest;
|
|
@@ -131,6 +133,12 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
131
133
|
private mastra: Mastra | null = null;
|
|
132
134
|
private mastraApp: express.Express | null = null;
|
|
133
135
|
private mastraServer: MastraServer | null = null;
|
|
136
|
+
/**
|
|
137
|
+
* The optional MCP server exposing this plugin's agents / tools, or
|
|
138
|
+
* `null` when `config.mcp` is disabled (the default). Built in
|
|
139
|
+
* {@link buildAgentAndServer} and registered on the Mastra instance.
|
|
140
|
+
*/
|
|
141
|
+
private mcp: ResolvedMcp | null = null;
|
|
134
142
|
/**
|
|
135
143
|
* Dedicated service-principal Lakebase pool backing Mastra memory /
|
|
136
144
|
* storage. Built once in {@link buildAgentAndServer} (outside any
|
|
@@ -207,6 +215,26 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
207
215
|
(this.built && this.built.agents[this.built.defaultAgentId]) ?? null,
|
|
208
216
|
/** Underlying Mastra instance for advanced use (custom routes etc.). */
|
|
209
217
|
getMastra: () => this.mastra,
|
|
218
|
+
/**
|
|
219
|
+
* MCP endpoint info when `config.mcp` is enabled, else `null`.
|
|
220
|
+
* Paths are absolute (under the plugin mount), ready to hand to an
|
|
221
|
+
* MCP client. Streamable HTTP is `http`; the SSE pair is the
|
|
222
|
+
* legacy transport.
|
|
223
|
+
*/
|
|
224
|
+
getMcp: (): {
|
|
225
|
+
serverId: string;
|
|
226
|
+
http: string;
|
|
227
|
+
sse: string;
|
|
228
|
+
messages: string;
|
|
229
|
+
} | null =>
|
|
230
|
+
this.mcp
|
|
231
|
+
? {
|
|
232
|
+
serverId: this.mcp.serverId,
|
|
233
|
+
http: `/api/${this.name}${this.mcp.httpPath}`,
|
|
234
|
+
sse: `/api/${this.name}${this.mcp.ssePath}`,
|
|
235
|
+
messages: `/api/${this.name}${this.mcp.messagePath}`,
|
|
236
|
+
}
|
|
237
|
+
: null,
|
|
210
238
|
/** Express subapp Mastra is mounted on; mostly for tests. */
|
|
211
239
|
getMastraServer: () => this.mastraServer,
|
|
212
240
|
/**
|
|
@@ -357,7 +385,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
357
385
|
// failure should leave the chat usable with a bare empty
|
|
358
386
|
// state rather than surfacing a 500. Log and degrade.
|
|
359
387
|
this.log.warn("suggestions:error", {
|
|
360
|
-
error:
|
|
388
|
+
error: commonUtils.errorMessage(err),
|
|
361
389
|
});
|
|
362
390
|
res.json({ questions: [] });
|
|
363
391
|
});
|
|
@@ -448,7 +476,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
448
476
|
} catch (err) {
|
|
449
477
|
// The Databricks SDK throws on 404; surface as `undefined`
|
|
450
478
|
// so the route maps to a clean HTTP 404 instead of a 500.
|
|
451
|
-
if (
|
|
479
|
+
if (apiUtils.isNotFoundError(err)) return undefined;
|
|
452
480
|
throw err;
|
|
453
481
|
}
|
|
454
482
|
}
|
|
@@ -536,10 +564,24 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
536
564
|
// `createApp`, so the OTLP endpoint / headers / sampling are
|
|
537
565
|
// env-driven and shared with every other AppKit plugin.
|
|
538
566
|
const observability = await buildObservability({ serviceName: this.name });
|
|
567
|
+
// Optional MCP exposure: build a Mastra MCP server from the
|
|
568
|
+
// registered agents (and, opt-in, the ambient tools) and register
|
|
569
|
+
// it on the Mastra instance. `@mastra/express` serves the stock MCP
|
|
570
|
+
// transport routes (`/mcp/<serverId>/...`) off `mcpServers`, so the
|
|
571
|
+
// catch-all dispatch below already routes MCP requests under OBO -
|
|
572
|
+
// no bespoke route needed. See `./mcp.js`.
|
|
573
|
+
this.mcp = buildMcpServer({
|
|
574
|
+
config: this.config,
|
|
575
|
+
pluginName: this.name,
|
|
576
|
+
displayName: MastraPlugin.manifest.displayName,
|
|
577
|
+
agents: this.built.agents,
|
|
578
|
+
ambientTools: this.built.ambientTools,
|
|
579
|
+
});
|
|
539
580
|
this.mastra = new Mastra({
|
|
540
581
|
agents: this.built.agents,
|
|
541
582
|
...(instanceStorage ? { storage: instanceStorage } : {}),
|
|
542
583
|
...(observability ? { observability } : {}),
|
|
584
|
+
...(this.mcp ? { mcpServers: { [this.mcp.serverId]: this.mcp.server } } : {}),
|
|
543
585
|
});
|
|
544
586
|
this.mastraApp = express();
|
|
545
587
|
attachRoutePatchMiddleware(this.mastraApp);
|
|
@@ -572,6 +614,7 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
572
614
|
routes: ["/route/history", "/models", "/suggestions", "/embed/:type/:id"],
|
|
573
615
|
instanceStorage: instanceStorage !== undefined,
|
|
574
616
|
observability: observability !== undefined ? "mlflow" : "off",
|
|
617
|
+
mcp: this.mcp ? `/api/${this.name}${this.mcp.httpPath}` : "off",
|
|
575
618
|
});
|
|
576
619
|
}
|
|
577
620
|
}
|
package/src/serving.ts
CHANGED
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
* onto the knobs `buildModel` and the `/models` route share.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import {
|
|
13
|
+
MODEL_OVERRIDE_BODY_FIELDS,
|
|
14
|
+
MODEL_OVERRIDE_HEADER,
|
|
15
|
+
MODEL_OVERRIDE_QUERY,
|
|
16
|
+
} from "@dbx-tools/appkit-mastra-shared";
|
|
12
17
|
import { DEFAULT_FUZZY_THRESHOLD, DEFAULT_MODEL_CACHE_TTL_MS } from "@dbx-tools/model";
|
|
13
18
|
import { stringUtils } from "@dbx-tools/shared";
|
|
14
19
|
|
|
@@ -21,15 +26,6 @@ import type { MastraPluginConfig } from "./config.js";
|
|
|
21
26
|
*/
|
|
22
27
|
export const MASTRA_MODEL_OVERRIDE_KEY = "mastra__model_override";
|
|
23
28
|
|
|
24
|
-
/** HTTP header inspected for a per-request model override. */
|
|
25
|
-
export const MODEL_OVERRIDE_HEADER = "x-mastra-model";
|
|
26
|
-
|
|
27
|
-
/** Query string parameter inspected for a per-request model override. */
|
|
28
|
-
export const MODEL_OVERRIDE_QUERY = "model";
|
|
29
|
-
|
|
30
|
-
/** Body fields (in priority order) inspected for a per-request model override. */
|
|
31
|
-
export const MODEL_OVERRIDE_BODY_FIELDS = ["model", "modelId"] as const;
|
|
32
|
-
|
|
33
29
|
/**
|
|
34
30
|
* Minimal Express-ish request shape used by {@link extractModelOverride}.
|
|
35
31
|
* Keeps this module independent of `express` so the helper can be
|
package/src/statement.ts
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Databricks Statement Execution helpers for the Mastra plugin.
|
|
3
3
|
*
|
|
4
|
-
* Wraps `client.statementExecution.getStatement` with the shape
|
|
5
|
-
* size
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Wraps `client.statementExecution.getStatement` with the shape and
|
|
5
|
+
* size handling the plugin's tools and the `/embed/data/:id` route
|
|
6
|
+
* both need: a low-level fetch that returns a raw
|
|
7
|
+
* `{columns, rows, rowCount}` shape and coerces numeric strings to
|
|
8
|
+
* numbers so downstream charts and aggregations don't have to, plus a
|
|
9
9
|
* hard row cap callers clamp `limit` to so a runaway result set can't
|
|
10
|
-
* hose a response
|
|
11
|
-
*
|
|
12
|
-
* `not found` message shapes into one boolean so the route can map
|
|
13
|
-
* upstream 404s to a clean HTTP 404 without coupling to SDK
|
|
14
|
-
* error-class identity.
|
|
10
|
+
* hose a response. Upstream 404s are detected via
|
|
11
|
+
* `apiUtils.isNotFoundError` at the call sites.
|
|
15
12
|
*
|
|
16
13
|
* Not Genie-specific: a Databricks `statement_id` is workspace
|
|
17
14
|
* scoped and lives in the Statement Execution API regardless of
|
|
@@ -20,7 +17,7 @@
|
|
|
20
17
|
* 404s without reaching into the Genie tool module.
|
|
21
18
|
*/
|
|
22
19
|
|
|
23
|
-
import {
|
|
20
|
+
import { WorkspaceClient } from "@databricks/sdk-experimental";
|
|
24
21
|
import type { GenieDatasetData } from "@dbx-tools/appkit-mastra-shared";
|
|
25
22
|
import { apiUtils } from "@dbx-tools/shared";
|
|
26
23
|
|
|
@@ -93,28 +90,3 @@ export async function fetchStatementData(
|
|
|
93
90
|
rowCount: r.manifest?.total_row_count ?? dataArray.length,
|
|
94
91
|
};
|
|
95
92
|
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* True when `err` looks like the Databricks SDK's "statement not
|
|
99
|
-
* found" error. Matches the typed {@link ApiError} 404 /
|
|
100
|
-
* `RESOURCE_DOES_NOT_EXIST` shape first, then falls back to the
|
|
101
|
-
* lower-level {@link HttpError} 404, then to a loose `does not
|
|
102
|
-
* exist` / `not found` message sniff for SDK shapes we haven't
|
|
103
|
-
* catalogued.
|
|
104
|
-
*
|
|
105
|
-
* Pulled into its own helper so callers (notably the
|
|
106
|
-
* `/embed/data/:id` route) stay decoupled from SDK
|
|
107
|
-
* error-class identity, and the conversion logic stays testable
|
|
108
|
-
* in isolation.
|
|
109
|
-
*/
|
|
110
|
-
export function isStatementNotFoundError(err: unknown): boolean {
|
|
111
|
-
if (err instanceof ApiError) {
|
|
112
|
-
if (err.statusCode === 404) return true;
|
|
113
|
-
if (err.errorCode === "RESOURCE_DOES_NOT_EXIST") return true;
|
|
114
|
-
}
|
|
115
|
-
if (err instanceof HttpError && err.code === 404) return true;
|
|
116
|
-
if (err instanceof Error && /does not exist|not found/i.test(err.message)) {
|
|
117
|
-
return true;
|
|
118
|
-
}
|
|
119
|
-
return false;
|
|
120
|
-
}
|