Skip to content

Class: MastraPlugin

AppKit plugin (registered name: mastra) that hosts Mastra agents with optional Lakebase-backed memory and AI SDK chat routes under the plugin mount (typically /api/mastra).

Register the plugin

import { createApp, lakebase } from "@databricks/appkit";
import { createAgent, mastra } from "@dbx-tools/appkit-mastra";
// `lakebase` first: registering it auto-enables Mastra storage + memory.
const app = await createApp({
plugins: [
lakebase(),
mastra({
genieSpaces: { default: process.env.DATABRICKS_GENIE_SPACE_ID! },
agents: createAgent({
name: "analyst",
instructions: "You answer questions about revenue and returns.",
tools(plugins) {
return { ...plugins.genie?.toolkit() };
},
}),
}),
],
});

Read the agents back off the AppKit instance

const agentIds = app.mastra.list();
const endpoints = await app.mastra.listModels();

new MastraPlugin(config): MastraPlugin

MastraPluginConfig

MastraPlugin

Plugin<MastraPluginConfig>.constructor

protected app: AppManager

Plugin.app


protected cache: CacheManager

Plugin.cache


protected config: MastraPluginConfig

Plugin.config


protected optional context?: PluginContext

Plugin.context


protected devFileReader: DevFileReader

Plugin.devFileReader


protected isReady: boolean

Plugin.isReady


name: string

Plugin name identifier.

Plugin.name


protected streamManager: StreamManager

Plugin.streamManager


protected telemetry: ITelemetry

Plugin.telemetry


static manifest: PluginManifest<"mastra">


static phase: PluginPhase

Plugin initialization phase.

  • ‘core’: Initialized first (e.g., config plugins)
  • ‘normal’: Initialized second (most plugins)
  • ‘deferred’: Initialized last (e.g., server plugin)

Plugin.phase

abortActiveOperations(): void

Abort in-flight work. AppKit’s graceful shutdown calls this hook synchronously and never awaits shutdown, so the pool drain is started here too; it cannot be awaited from a void hook, and shutdown is idempotent so the duplicate call is free.

void

Plugin.abortActiveOperations


asUser(req): this

Execute operations using the user’s identity from the request. Returns a proxy of this plugin where all method calls execute with the user’s Databricks credentials instead of the service principal.

Request

The Express request containing the user token in headers

this

A proxied plugin instance that executes as the user

AuthenticationError if user token is not available in request headers (production only). In development mode (NODE_ENV=development), skips user impersonation instead of throwing.

Plugin.asUser


attachContext(deps?): void

Binds runtime dependencies (telemetry provider, cache, plugin context) to this plugin. Called by AppKit._createApp after construction and before setup(). Idempotent: safe to call if the constructor already bound them eagerly. Kept separate so factories can eagerly construct plugin instances without running this before TelemetryManager.initialize() / CacheManager.getInstance() have run.

unknown

TelemetryOptions

void

Plugin.attachContext


clientConfig(): Record<string, unknown>

Returns startup config to expose to the client. Override this to surface server-side values that are safe to publish to the frontend, such as feature flags, resource IDs, or other app boot settings.

This runs once when the server starts, so it should not depend on request-scoped or user-specific state.

String values that match non-public environment variables are redacted unless you intentionally expose them via a matching PUBLIC_APPKIT_ env var.

Values must be JSON-serializable plain data (no functions, Dates, classes, Maps, Sets, BigInts, or circular references). By default returns an empty object (plugin contributes nothing to client config).

On the client, read the config with the usePluginClientConfig hook (React) or the getPluginClientConfig function (vanilla JS), both from @databricks/appkit-ui.

Record<string, unknown>

// Server — plugin definition
class MyPlugin extends Plugin<MyConfig> {
clientConfig() {
return {
warehouseId: this.config.warehouseId,
features: { darkMode: true },
};
}
}
// Client — React component
import { usePluginClientConfig } from "@databricks/appkit-ui/react";
interface MyPluginConfig { warehouseId: string; features: { darkMode: boolean } }
const config = usePluginClientConfig<MyPluginConfig>("myPlugin");
config.warehouseId; // "abc-123"
// Client — vanilla JS
import { getPluginClientConfig } from "@databricks/appkit-ui/js";
const config = getPluginClientConfig<MyPluginConfig>("myPlugin");

Plugin.clientConfig


protected execute<T>(fn, options, userKey?): Promise<ExecutionResult<T>>

Execute a function with the plugin’s interceptor chain.

Returns an ExecutionResult discriminated union:

  • { ok: true, data: T } on success
  • { ok: false, status: number, message: string } on failure

Errors are never thrown — the method is production-safe.

T

(signal?) => Promise<T>

PluginExecutionSettings

string

Promise<ExecutionResult<T>>

Plugin.execute


protected executeStream<T>(res, fn, options, userKey?): Promise<void>

T

IAppResponse

StreamExecuteHandler<T>

StreamExecutionSettings

string

Promise<void>

Plugin.executeStream


exports(): object

Returns the public exports for this plugin. Override this to define a custom public API. By default, returns an empty object.

The returned object becomes the plugin’s public API on the AppKit instance (e.g. appkit.myPlugin.method()). AppKit automatically binds method context and adds asUser(req) for user-scoped execution.

clearModelsCache: (host?) => Promise<void>

Force-evict cached endpoint listings via AppKit’s CacheManager. Useful in tests or right after an admin deploys a new endpoint and doesn’t want to wait for the TTL. Returns the underlying CacheManager.delete/clear promise.

string

Promise<void>

createRequestContext: (options) => Promise<RequestContext<unknown>>

Build the RequestContext an agent turn driven from OUTSIDE this plugin’s routes needs (a Teams activity on another plugin’s endpoint, a scheduled job).

Mastra’s user-scoped tools - ask_genie most visibly - read the AppKit user off the request context, which only the HTTP middleware stamps. Calling agent.generate with a raw prompt therefore answers “the data source is unreachable” where the chat routes answer with real data. Passing this as requestContext closes that gap, so an out-of-band turn has exactly the capabilities a chat turn has.

Must be called INSIDE an asUser(req) scope to inherit the caller’s OBO identity; outside one it resolves to the service principal.

string

string

string

Promise<RequestContext<unknown>>

get: (id) => Agent<string, ToolsInput, undefined, unknown, AgentEditorConfig | undefined> | null

Look up a registered agent by id. Returns null (not undefined) when unknown so call sites can early-return without a separate in check.

string

Agent<string, ToolsInput, undefined, unknown, AgentEditorConfig | undefined> | null

getDefault: () => Agent<string, ToolsInput, undefined, unknown, AgentEditorConfig | undefined> | null

The agent the client converses with when it doesn’t name one. Resolves to config.defaultAgent, the first registered id, or the built-in default fallback.

Agent<string, ToolsInput, undefined, unknown, AgentEditorConfig | undefined> | null

getMastra: () => Mastra<Record<string, Agent<any, ToolsInput, undefined, unknown, AgentEditorConfig | undefined>>, Record<string, AnyWorkflow>, Record<string, MastraVector<any>>, Record<string, MastraTTS>, IMastraLogger, Record<string, MCPServerBase<any>>, Record<string, MastraScorer<any, any, any, any>>, Record<string, ToolAction<any, any, any, any, any, any, unknown>>, Record<string, Processor<any, unknown>>, Record<string, MastraMemory>, Record<string, ChannelProvider>> | null

Underlying Mastra instance for advanced use (custom routes etc.).

Mastra<Record<string, Agent<any, ToolsInput, undefined, unknown, AgentEditorConfig | undefined>>, Record<string, AnyWorkflow>, Record<string, MastraVector<any>>, Record<string, MastraTTS>, IMastraLogger, Record<string, MCPServerBase<any>>, Record<string, MastraScorer<any, any, any, any>>, Record<string, ToolAction<any, any, any, any, any, any, unknown>>, Record<string, Processor<any, unknown>>, Record<string, MastraMemory>, Record<string, ChannelProvider>> | null

getMastraServer: () => MastraServer | null

Express subapp Mastra is mounted on; mostly for tests.

MastraServer | null

getMcp: () => { http: string; httpPath: string; messagePath: string; messages: string; serverId: string; sse: string; ssePath: string; } | null

MCP endpoint info when config.mcp is enabled, else null. Streamable HTTP is http; the SSE pair is the legacy transport.

Each path is given twice: mount-relative (httpPath, ssePath, messagePath) for anything that already knows where the plugin is mounted, and absolute (http, sse, messages) for MCP clients, which take a single URL and cannot compose one. The absolute form is built from basePath, so it honors a config.name override but still assumes AppKit’s default /api/<name> mount.

{ http: string; httpPath: string; messagePath: string; messages: string; serverId: string; sse: string; ssePath: string; } | null

list: () => string[]

Ids of every registered agent in registration order. Matches AppKit agents.list() so callers can iterate the registry the same way under both plugins.

string[]

listModels: () => Promise<object[]>

Fetch the workspace’s Model Serving endpoints (cached). Same payload the GET /models route returns; surfaced here so other plugins / scripts can introspect the catalogue without an HTTP round-trip. AppKit wraps this with asUser(req) for OBO scoping automatically. Throws when the listing fails, since there is no status code to hand back on this surface.

Promise<object[]>

class MyPlugin extends Plugin {
private getData() { return []; }
exports() {
return { getData: this.getData };
}
}
// After registration:
const appkit = await createApp({ plugins: [myPlugin()] });
appkit.myPlugin.getData();

Plugin.exports


getEndpoints(): PluginEndpointMap

PluginEndpointMap

Plugin.getEndpoints


getSkipBodyParsingPaths(): ReadonlySet<string>

ReadonlySet<string>

Plugin.getSkipBodyParsingPaths


injectRoutes(router): void

Router

void

Plugin.injectRoutes


protected registerEndpoint(name, path): void

string

string

void

Plugin.registerEndpoint


protected resolveUserId(req): string

Resolve the effective user ID from a request.

Returns the x-forwarded-user header when present. In development mode (NODE_ENV=development) falls back to the current context user ID so that callers outside an active runInUserContext scope still get a consistent value.

Request

string

AuthenticationError in production when no user header is present.

Plugin.resolveUserId


protected route<_TResponse>(router, config): void

_TResponse

Router

RouteConfig

void

Plugin.route


setup(): Promise<void>

Promise<void>

Plugin.setup


shutdown(): Promise<void>

Drain the memory service-principal pool. Idempotent: the handle is cleared before the drain starts, so a second call is a no-op and a later setup() rebuilds the pool. Bounded by POOL_DRAIN_TIMEOUT_MS to stay well inside the 15s graceful shutdown budget.

Promise<void>


static getResourceRequirements(config): ResourceRequirement[]

Tighten resource requirements based on which features are enabled. AppKit calls this at registration time (config-aware) so disabled features don’t surface their resource asks to the host app.

MastraPluginConfig

ResourceRequirement[]