Tools & toLayer
MastraTool.make
Define tools with Effect Schema at the boundary:
import { Schema } from "effect"
import { MastraTool } from "@mastra-effect/core"
const SearchDocs = MastraTool.make({
id: "search_docs",
description: "Search docs",
inputSchema: Schema.Struct({ query: Schema.String }),
outputSchema: Schema.String,
})Each tool exposes:
tool.execute(params, context)— Effect-native execution
Bridge tools to Mastra's agent loop via toolkit.toMastraTools() (see Toolkit below).
Toolkit
import { Toolkit } from "@mastra-effect/core"
const toolkit = Toolkit.make(SearchDocs, OtherTool)| Method | Description |
|---|---|
Toolkit.make(...tools) | Build a typed toolkit |
toolkit.toLayer(build) | Register server handlers as Layer<ToolHandlers, E, R> |
toolkit.toMastraTools() | Build Mastra ToolsInput for the agent loop |
Toolkit.merge(...toolkits) | Merge toolkits |
Effect-ful toLayer
Pass an Effect callback that returns the server handler map. Layer-level setup deps can be yield*d in the outer callback; handler deps are inferred from inner handler effects.
const handlersLayer = toolkit.toLayer(
Effect.fn(function* () {
return {
search_docs: Effect.fn(function* ({ query }) {
const api = yield* ApiKey
return `${api.token}:${query}`
}),
}
})(),
)
// Layer<ToolHandlers, never, typeof ApiKey>Plain handler maps still type-check as compile-time sugar (wrapped at runtime). Documentation and examples use the Effect-ful form.
TypeScript rejects requiredClient tool keys in toLayer — client tools are wired through Assistant UI instead.
Inferred handler R
toolkit.toLayer returns Layer<ToolHandlers, E, R> where R is inferred from:
- Services the outer callback
yield*s - Services each server handler
yield*s
Provide those layers per agent run. Long-lived layers (MastraAgent, Memory) stay separate.
requiredClient tools
const PickAsset = MastraTool.make({
id: "pick_asset",
description: "Pick an asset in the browser",
requiredClient: true,
inputSchema: Schema.Struct({ assetId: Schema.String }),
})requiredClient | Execute path |
|---|---|
true | Fulfilled on the client via Assistant UI addResult. Server execute throws ClientToolRequiredError. |
omitted / false | Server handler via toolkit.toLayer. |
Stream abort and tool interruption
When a Mastra agent run or stream is cancelled, Mastra forwards an abortSignal on the tool execute context (ToolExecutionContext.abortSignal in @mastra/core). @mastra-effect/core propagates that signal into Effect so in-flight tool handlers stop promptly:
| Path | Behavior |
|---|---|
toolkit.toMastraTools() | Effect.runPromise(handler, { signal: context.abortSignal }) at the Mastra boundary |
tool.execute / executeEffectTool | Handler effect races against abortSignal; aborted runs fail with an interrupt |
Agent.stream → fromMastraOutput | Interrupting the returned Effect Stream cancels the underlying ReadableStream |
Handlers that observe cancellation should use interruptible effects (Effect.sleep, Effect.tryPromise with the Effect-provided signal, etc.). When the consumer disconnects from an agent stream, Mastra aborts the run and tool calls receive the same abortSignal.