Skip to main content
PromptSmith provides seamless integration with Mastra, a full-featured agent framework with workflows, memory, and observability. The key benefit: no tool duplication - define tools once in PromptSmith and they’re automatically converted to Mastra format.

Installation

1

Install dependencies

Install PromptSmith, Zod, and Mastra:
npm install promptsmith-ts zod @mastra/core
2

Optional: Install Mastra CLI

For advanced features like workflows and observability:
npm install -D @mastra/cli

Basic Integration

Use the toMastra() method to export both instructions and tools in Mastra format:
import { Agent } from "@mastra/core/agent";
import { createPromptBuilder } from "promptsmith-ts/builder";

const promptBuilder = createPromptBuilder()
  .withIdentity("You are a travel planning assistant")
  .withCapabilities([
    "Recommend destinations based on preferences",
    "Plan detailed itineraries",
    "Provide travel tips and advice",
  ])
  .withTone("Enthusiastic, knowledgeable, and helpful");

// Export to Mastra format
const { instructions, tools } = promptBuilder.toMastra();

// Create Mastra agent
const travelAgent = new Agent({
  name: "travel-planner",
  instructions,
  model: "openai/gpt-4o",
  tools,
});

// Generate response
const response = await travelAgent.generate([
  {
    role: "user",
    content: "I want to visit Japan for 2 weeks. What should I see?",
  },
]);

console.log(response.text);

No Tool Duplication

The major advantage of using PromptSmith with Mastra is that you define tools once and get both prompt documentation and runtime execution automatically.

Traditional Approach (Duplicated Effort)

// ❌ Without PromptSmith: Define tools twice

// 1. Define in prompt (manual, error-prone)
const instructions = `
You are a weather assistant.

Available tools:
- weatherTool: Get current weather
  Parameters:
  - location (string, required): City name
  - units (string, optional): celsius or fahrenheit
`;

// 2. Define again for Mastra (must stay in sync!)
const weatherTool = createTool({
  id: "weatherTool",
  description: "Get current weather for a location",
  inputSchema: z.object({
    location: z.string(),
    units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
  }),
  execute: async ({ context }) => {
    return await fetchWeather(context.location, context.units);
  },
});

const agent = new Agent({
  name: "weather-agent",
  instructions, // Manual prompt
  model: "openai/gpt-4o",
  tools: { weatherTool }, // Separate tool definition
});

PromptSmith Approach (Define Once)

import { Agent } from "@mastra/core/agent";
import { createPromptBuilder } from "promptsmith-ts/builder";
import { z } from "zod";

// ✅ With PromptSmith: Define once, use everywhere
const promptBuilder = createPromptBuilder()
  .withIdentity("Weather information assistant")
  .withCapabilities(["Provide current weather conditions"])
  .withTool({
    name: "weatherTool",
    description: "Get current weather for a location",
    schema: z.object({
      location: z.string(),
      units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
    }),
    // ✅ Single definition - automatically adapted for Mastra
    execute: async ({ location, units }) => {
      return await fetchWeather(location, units);
    },
  })
  .withConstraint("must", "Always use the weather tool for current conditions")
  .withTone("Friendly and informative");

// ✅ Automatic conversion to Mastra format
const { instructions, tools } = promptBuilder.toMastra();

const weatherAgent = new Agent({
  name: "weather-assistant",
  instructions, // Generated with tool documentation
  model: "anthropic/claude-3-5-sonnet",
  tools, // Converted to Mastra format: { weatherTool: {...} }
});

Using Mastra Tools with PromptSmith

PromptSmith can also consume tools created with Mastra’s createTool() function. The framework automatically detects and converts them:
import { Agent } from "@mastra/core/agent";
import { createTool } from "@mastra/core/tools";
import { createPromptBuilder } from "promptsmith-ts/builder";
import { z } from "zod";

// Create tool using Mastra's createTool()
const weatherTool = createTool({
  id: "weatherTool",
  description: "Get current weather for a location",
  inputSchema: z.object({
    location: z.string(),
    units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
  }),
  outputSchema: z.object({
    temperature: z.number(),
    conditions: z.string(),
  }),
  execute: async ({ context }) => {
    // Mastra's signature: receives { context }
    return {
      temperature: context.units === "celsius" ? 22 : 72,
      conditions: "Partly cloudy",
    };
  },
});

// ✅ PromptSmith automatically detects and converts Mastra tools
const agent = createPromptBuilder()
  .withIdentity("Weather assistant")
  .withCapabilities(["Provide weather information"])
  .withTool(weatherTool) // ← Mastra tool works directly!
  .withTone("Friendly and informative");

const { instructions, tools } = agent.toMastra();

Mixing PromptSmith and Mastra Tools

You can use both tool formats seamlessly in the same agent:
import { createTool } from "@mastra/core/tools";
import { createPromptBuilder } from "promptsmith-ts/builder";
import { z } from "zod";

// Mastra tool (from existing codebase)
const searchTool = createTool({
  id: "searchTool",
  description: "Search the knowledge base",
  inputSchema: z.object({ query: z.string() }),
  execute: async ({ context }) => {
    return `Search results for: ${context.query}`;
  },
});

// PromptSmith tool (new tool)
const calculateTool = {
  name: "calculateTool",
  description: "Perform calculations",
  schema: z.object({
    expression: z.string(),
  }),
  execute: async ({ expression }) => {
    return { result: eval(expression) };
  },
};

// ✅ Both formats work together
const agent = createPromptBuilder()
  .withIdentity("Multi-purpose assistant")
  .withTool(searchTool) // ← Mastra format
  .withTool(calculateTool) // ← PromptSmith format
  .withConstraint("must", "Always verify calculations");

const { instructions, tools } = agent.toMastra();
// tools = { searchTool: {...}, calculateTool: {...} }

Advanced: Multi-Agent Workflow

Combine PromptSmith with Mastra’s workflow capabilities:
import { Agent } from "@mastra/core/agent";
import { Workflow } from "@mastra/core/workflows";
import { createPromptBuilder } from "promptsmith-ts/builder";
import { z } from "zod";

// Research agent
const researchBuilder = createPromptBuilder()
  .withIdentity("Research specialist")
  .withCapabilities(["Search papers", "Analyze research"])
  .withTool({
    name: "search_papers",
    description: "Search academic papers",
    schema: z.object({ topic: z.string() }),
    execute: async ({ topic }) => {
      return await academicDB.search(topic);
    },
  });

const researchAgent = new Agent({
  name: "researcher",
  ...researchBuilder.toMastra(),
  model: "openai/gpt-4o",
});

// Writing agent
const writerBuilder = createPromptBuilder()
  .withIdentity("Technical writer")
  .withCapabilities(["Write summaries", "Create reports"])
  .withTone("Clear and concise");

const writerAgent = new Agent({
  name: "writer",
  ...writerBuilder.toMastra(),
  model: "anthropic/claude-3-5-sonnet",
});

// Create workflow
const workflow = new Workflow({
  name: "research-report",
  steps: [
    {
      id: "research",
      agent: researchAgent,
      input: (ctx) => ctx.topic,
    },
    {
      id: "write",
      agent: writerAgent,
      input: (ctx, prev) => `Summarize: ${prev.research}`,
    },
  ],
});

const result = await workflow.execute({ topic: "AI Safety" });

How Tool Conversion Works

When you call toMastra(), PromptSmith automatically converts tools:
// PromptSmith format
{
  name: "weatherTool",
  description: "Get weather",
  schema: z.object({ location: z.string() }),
  execute: async ({ location }) => {
    return await fetchWeather(location);
  }
}

// Converted to Mastra format
{
  weatherTool: {
    id: "weatherTool",
    description: "Get weather",
    inputSchema: z.object({ location: z.string() }),
    outputSchema: undefined, // Optional in Mastra
    execute: async ({ context }) => {
      // Wrapped to adapt signature
      return await fetchWeather(context.location);
    }
  }
}
Key conversions:
  • nameid
  • schemainputSchema
  • execute({ param })execute({ context: param })
  • Returns object format: { toolName: {...} } (Mastra’s expected format)

What’s Exported

The toMastra() method returns:
type MastraConfig = {
  // The complete system prompt as instructions
  instructions: string;

  // Tools in Mastra format (object, not array)
  tools: Record<
    string,
    {
      id: string;
      description: string;
      inputSchema: z.ZodType;
      outputSchema?: z.ZodType;
      execute?: (args: { context: unknown }) => Promise<unknown> | unknown;
    }
  >;
};

Production Example: Customer Support Agent

import { Agent } from "@mastra/core/agent";
import { createPromptBuilder } from "promptsmith-ts/builder";
import { z } from "zod";

const supportAgent = createPromptBuilder()
  .withIdentity("Customer support assistant for TechStore")
  .withCapabilities([
    "Answer product questions",
    "Help with orders",
    "Provide recommendations",
  ])
  .withContext(`
    Company Information:
    - Support hours: Monday-Friday, 9 AM - 5 PM EST
    - Return policy: 30 days with receipt
    - Free shipping on orders over $50
  `)
  .withTool({
    name: "search_products",
    description: "Search product catalog",
    schema: z.object({
      query: z.string().describe("Search query"),
      category: z.string().optional().describe("Product category"),
    }),
    execute: async ({ query, category }) => {
      return await db.products.search({ query, category });
    },
  })
  .withTool({
    name: "get_order_status",
    description: "Get order status and tracking",
    schema: z.object({
      orderId: z.string().describe("Order ID"),
    }),
    execute: async ({ orderId }) => {
      return await db.orders.findOne({ id: orderId });
    },
  })
  .withGuardrails()
  .withForbiddenTopics(["Internal company information", "Competitor pricing"])
  .withTone("Professional, helpful, and empathetic");

const { instructions, tools } = supportAgent.toMastra();

const agent = new Agent({
  name: "support-agent",
  instructions,
  model: "anthropic/claude-3-5-sonnet",
  tools,
});

// Use in production
const response = await agent.generate([
  {
    role: "user",
    content: "I want to return my laptop. What's the process?",
  },
]);

console.log(response.text);

Best Practices

Use PromptSmith’s tool definitions as the single source of truth:
const builder = createPromptBuilder()
  .withTool({
    name: "tool_name",
    description: "What the tool does",
    schema: z.object({ param: z.string() }),
    execute: async ({ param }) => {
      // Implementation
    },
  });

const { instructions, tools } = builder.toMastra();
// Tools are automatically in Mastra format
Leverage Zod for full type safety:
schema: z.object({
  query: z.string().describe("Search query"),
  limit: z.number().min(1).max(100).default(10),
})
Use constraints to guide agent behavior:
builder
  .withConstraint("must", "Always verify order IDs before processing")
  .withConstraint("must_not", "Never share customer information");
Always enable guardrails for production agents:
builder
  .withGuardrails()
  .withForbiddenTopics(["Sensitive topic 1", "Sensitive topic 2"]);

Comparison: PromptSmith vs Native Mastra

FeatureNative MastraWith PromptSmith
Tool DefinitionManual prompt + createTool()Single definition
Type SafetyManual schemasFull Zod inference
Prompt ManagementString templatesStructured builder
SecurityDIYBuilt-in guardrails
TestingManualAutomated framework
ReusabilityLimitedCompose & extend
DocumentationManual syncAuto-generated

Next Steps

Vercel AI SDK

Use PromptSmith with Vercel AI SDK

Other Frameworks

Integrate with other AI frameworks

Build docs developers (and LLMs) love