Connect: Mastra¶
Recipe · through our MCP server or the TypeScript client · their docs checked 2026-10-30
Two ways in. MCP loads every tool in one call. The TS client keeps approvals in Python:
decide runs the call, so confirm tools work end to end.
Our side¶
def public_app(tools: Registry, host: str, agent_token: str, reviewer_token: str) -> Any:
"""REST at /, MCP at /mcp, one bearer check for both: for a host on the internet."""
from mcp.server.transport_security import TransportSecuritySettings
from agent_adapter.server import create_app
tokens = {
agent_token: Principal("hosted-agent"), # the token the remote framework sends
reviewer_token: Principal("reviewer@example.com", scopes=frozenset({APPROVER_SCOPE})),
}
security = TransportSecuritySettings(allowed_hosts=[host], allowed_origins=[f"https://{host}"])
return create_app(
tools,
authenticate=bearer_auth(tokens),
host="0.0.0.0", # a public bind: the public-host checks apply
allowed_hosts=[host], # REST: any other Host header gets 421
mcp_options={"transport_security": security}, # MCP: the same rule
)
In development on one machine, local_app() (MCP only) is enough for the MCP route.
Their side: MCP¶
import { MCPClient } from "@mastra/mcp";
const mcp = new MCPClient({
servers: {
shopTools: {
url: new URL("https://tools.example.com/mcp"),
requestInit: { headers: { Authorization: `Bearer ${process.env.AA_AGENT_TOKEN}` } },
requireToolApproval: ({ toolName }) => toolName === "send_invoice", // UI gate only
},
},
timeout: 60_000,
});
const tools = await mcp.listTools(); // names are namespaced by server
| Key | Meaning |
|---|---|
servers.<name>.url |
our /mcp endpoint, as a URL |
servers.<name>.requestInit.headers |
carries the bearer token |
servers.<name>.requireToolApproval |
boolean or a function: Mastra asks a person first |
timeout |
per request, in milliseconds (default 60 000) |
listTools() / listToolsets() |
tools for an agent definition / per call |
Their side: TS client¶
import { ToolClient } from "@agent-adapter/client";
import { createTool } from "@mastra/core/tools";
const client = new ToolClient({ baseUrl: "https://tools.example.com", token: process.env.AA_AGENT_TOKEN });
const defs = await client.toolDefinitions({ runId: "run_42" });
// each def is { name, description, inputSchema, execute }: createTool({ id: def.name, ... })
Mastra's createTool takes id, description, inputSchema and execute. Its
inputSchema is a Standard JSON Schema (Zod, Valibot, ArkType), not a plain JSON Schema
object: convert def.inputSchema with a library that supports that standard, or use the
MCP route. execute returns the result, or { pending_approval: approval_id } while a reviewer
decides with client.decide(...). The package is private for now: use it from this
repository.
Checklist¶
| Check | Why |
|---|---|
requireToolApproval is extra |
it only adds a gate in Mastra's UI; our executor still decides confirm tools |
over MCP, serve hosted or keep confirm tools for the TS client route |
Mastra's MCPClient does not send our resume keys back |
| a browser app calls the TS client through your backend | the server sends no CORS headers, and the token must stay server-side |
| everything on the public-host checklist |
Sources¶
- Mastra
MCPClientreference: https://mastra.ai/reference/tools/mcp-client - Mastra
createToolreference: https://mastra.ai/reference/tools/create-tool - Our TS client:
clients/typescript/README.md