Skip to content

Tools & toLayer

MastraTool.make

Define tools with Effect Schema at the boundary:

ts
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

ts
import { Toolkit } from "@mastra-effect/core"

const toolkit = Toolkit.make(SearchDocs, OtherTool)
MethodDescription
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.

ts
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:

  1. Services the outer callback yield*s
  2. Services each server handler yield*s

Provide those layers per agent run. Long-lived layers (MastraAgent, Memory) stay separate.

requiredClient tools

ts
const PickAsset = MastraTool.make({
  id: "pick_asset",
  description: "Pick an asset in the browser",
  requiredClient: true,
  inputSchema: Schema.Struct({ assetId: Schema.String }),
})
requiredClientExecute path
trueFulfilled on the client via Assistant UI addResult. Server execute throws ClientToolRequiredError.
omitted / falseServer handler via toolkit.toLayer.

See Client tools & UiToolkit.

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:

PathBehavior
toolkit.toMastraTools()Effect.runPromise(handler, { signal: context.abortSignal }) at the Mastra boundary
tool.execute / executeEffectToolHandler effect races against abortSignal; aborted runs fail with an interrupt
Agent.streamfromMastraOutputInterrupting 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.