Mastra Workshop
STDIOMCP workshop demonstrating best practices with customer analytics server featuring workflow-oriented tools
MCP workshop demonstrating best practices with customer analytics server featuring workflow-oriented tools
A hands-on workshop demonstrating MCP (Model Context Protocol) best practices using Mastra. Build a "Customer Analytics" MCP server that showcases workflow-oriented tools, authentication patterns, and multi-system integration.
Customer Analytics MCP Server featuring:
compute_account_health (multi-system workflow), run_sql (database queries)schema://main (discovery & exploration)# Required node >= 20.9.0 pnpm >= 8.0.0 # Optional: OpenAI API key for demo export OPENAI_API_KEY="your-key-here"
# Clone and install git clone <this-repo> cd mcp-server-workshop-best-practices pnpm install
# Terminal 1: Start HTTP MCP server with real auth pnpm mcp-http-server # Terminal 2: Run HTTP workshop demo pnpm workshop-demo-http # Server runs on http://localhost:3001 with endpoints: # - /mcp (MCP endpoint with auth) # - /health (health check)
// src/mastra/mcp/server.ts import { MCPServer } from "@mastra/mcp"; import { computeAccountHealthTool, runSqlTool } from "../tools"; const server = new MCPServer({ name: "customer-analytics", version: "0.5.0", description: "Customer analytics MCP server with multi-system workflows", tools: { compute_account_health: computeAccountHealthTool, run_sql: runSqlTool, }, resources: resourceHandlers, });
Key Features:
compute_account_health combines database, external APIs, and business logicschema://main resource// src/workshop-demo.ts import { MCPClient } from "@mastra/mcp"; import { Agent } from "@mastra/core/agent"; const mcpClient = new MCPClient({ servers: { customerAnalytics: { command: "pnpm", args: ["mcp-server"], env: { DEMO_API_KEY: "api_key_user_456" }, // Auth context }, }, }); const agent = new Agent({ name: "Customer Analytics Agent", model: openai("gpt-4o-mini"), }); // Use MCP tools with agent const response = await agent.generate(task, { toolsets: await mcpClient.getToolsets(), });
The workshop includes a production-ready HTTP server with real authentication:
// src/mastra/mcp/http-server.ts import { MCPServer } from "@mastra/mcp"; import { server } from "./server"; import type { AuthInfo, DemoUserInfo } from "./utils"; // Authentication middleware function authenticateRequest(req: http.IncomingMessage): AuthInfo | null { const authHeader = req.headers.authorization; // Support JWT Bearer tokens if (authHeader?.startsWith("Bearer ")) { return validateJWT(authHeader.slice(7)); } // Support API keys if (authHeader?.startsWith("ApiKey ")) { return validateApiKey(authHeader.slice(7)); } return null; } // HTTP request handler with MCP-compliant auth const handleRequest = async (req, res) => { const authInfo = authenticateRequest(req); if (!authInfo) { res.writeHead(401, { "Content-Type": "application/json" }); res.end( JSON.stringify({ error: "Authentication required", message: "Please provide a valid Authorization header", }), ); return; } // Attach MCP-compliant auth to request (req as any).auth = authInfo; await server.startHTTP({ url: new URL(`http://localhost:${PORT}`), httpPath: "/mcp", req, res, }); };
Key Features:
AuthInfo type from MCP specificationoptions.extra.authInfoTest Credentials:
# Health check (no auth required) curl localhost:3001/health # MCP endpoint (requires auth) curl -H "Authorization: ApiKey sk-user-987654321" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}' \ localhost:3001/mcp # Available credentials: # API Keys: sk-admin-123456789, sk-user-987654321, sk-readonly-555666777 # JWT Tokens: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.admin, eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.user
compute_account_health - Multi-System WorkflowPurpose: Demonstrates complex workflow patterns that combine multiple data sources for business insights.
Data Flow:
Internal DB (orders) → External NPS API → External Support API → Risk Scoring → Actionable Insights
Key Features:
Input Parameters:
segment: "all" | "inactive" | "highValue"windowDays: Analysis period (default 90 days)limit: Maximum accounts to analyze (default 50)includeReasons: Include risk factor explanationsOutput Structure:
{ "accounts": [ { "accountId": "1", "name": "Alice Johnson", "healthScore": 85, "tier": "good", "metrics": { "lastOrderDays": 5, "spendDeltaPct": 15.2, "nps": 72 }, "reasons": [] } ], "summary": { "totalAnalyzed": 20, "segmentBreakdown": { "good": 15, "watch": 3, "at_risk": 2 }, "avgHealthScore": 75, "externalDataCoverage": { "npsAvailable": 18, "supportDataAvailable": 20 } } }
run_sql - Database Query ToolPurpose: Safe, controlled database access with automatic guardrails.
Key Features:
Run the same task with multiple models:
Task: "Analyze customer health for high-value accounts and show database schema"
Expected flow:
1. Schema resource → understand available data structure
2. compute_account_health → multi-system customer analysis
3. run_sql → supplementary database queries if needed
4. Valid JSON response with insights and recommendations
Testing Matrix:
run_sql (workflow) vs getUserById (endpoint)Add a third tool to show workflow composition:
const cityReportTool = createTool({ id: "make_city_report", description: "Generate a comprehensive city analysis report", execute: async ({ context }) => { // Internally runs 2-3 SQL queries // Returns: { city, userCount, totalSpend, avgOrderValue }[] }, });
Replace the in-memory data with real database connections:
// Update src/mcp-server/server.ts import Database from "better-sqlite3"; const db = new Database("path/to/your/database.db"); // or with PostgreSQL import postgres from "postgres"; const sql = postgres("postgresql://..."); // Update executeQuery function to use real SQL
Add models to the compatibility test:
// src/workshop-demo.ts const MODELS_TO_TEST = [ { name: "GPT-4o Mini", model: openai("gpt-4o-mini") }, { name: "GPT-4o", model: openai("gpt-4o") }, { name: "Claude Sonnet", model: anthropic("claude-3-sonnet-20240229") }, // Add your preferred models ];
Found an issue or want to improve the workshop?
Built with ❤️ using Mastra - The TypeScript AI Framework