Skip to content

mastra-ai/mcp-server-best-practice-workshop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mastra MCP Workshop: Best Practices

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.

🎯 Workshop Goals

  • Minimize surface area: 2 tools that handle many use cases
  • Workflow-shaped tools: Capabilities (explore schema, run queries) vs endpoints (getUsers)
  • Model compatibility: Test with multiple models, ensure consistent behavior
  • Exploration: Let AI discover capabilities through resources
  • Guardrails: Safe, deterministic, read-only by default

🏗️ What We'll Build

Customer Analytics MCP Server featuring:

  • 📊 2 Tools: compute_account_health (multi-system workflow), run_sql (database queries)
  • 📚 1 Resource: schema://main (discovery & exploration)
  • 🔄 Workflow Patterns: External API integration, data fusion, scoring algorithms
  • 🛡️ Built-in Safety: SELECT-only, implicit LIMIT, parsed queries, API rate limits
  • 🔐 Authentication: Transparent role-based access control (admin/user/readonly)

🚀 Quick Start

Prerequisites

# Required
node >= 20.9.0
pnpm >= 8.0.0

# Optional: OpenAI API key for demo
export OPENAI_API_KEY="your-key-here"

Installation

# 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)

🛠️ Technical Deep Dive

MCP Server Implementation

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

  • Multi-system workflows: compute_account_health combines database, external APIs, and business logic
  • Resource-based discovery: Schema exploration via schema://main resource
  • Transparent authentication: MCP-compliant auth context passed to tools
  • Role-based access: admin/user/readonly permissions enforced per tool call
  • Guardrails built-in: SELECT-only, auto-LIMIT, permission checking
  • Error teaching: Structured, helpful error messages

Mastra Integration

// 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(),
});

HTTP Authentication (Production Pattern)

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:

  • JWT & API Key Support: Multiple authentication methods
  • MCP-Compliant Auth: Uses official AuthInfo type from MCP specification
  • Real Auth Context: Authentication info passed to tools via options.extra.authInfo
  • Session Management: Unique session IDs for each connection
  • CORS Support: Web client compatibility
  • Structured Errors: Clear auth failure responses

Test 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

🛠️ Tools Overview

1. compute_account_health - Multi-System Workflow

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

  • Multi-source data fusion: Order history + NPS scores + support tickets
  • Business logic: Configurable scoring weights (recency 30%, momentum 30%, satisfaction 25%, reliability 15%)
  • Segmentation: Filter by customer value, activity patterns, risk levels
  • External API simulation: Realistic delays, missing data, enterprise customer patterns
  • Role-based limits: Readonly users limited to 10 accounts

Input Parameters:

  • segment: "all" | "inactive" | "highValue"
  • windowDays: Analysis period (default 90 days)
  • limit: Maximum accounts to analyze (default 50)
  • includeReasons: Include risk factor explanations

Output 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 }
  }
}

2. run_sql - Database Query Tool

Purpose: Safe, controlled database access with automatic guardrails.

Key Features:

  • SELECT-only validation
  • Automatic LIMIT injection
  • Role-based row limiting
  • Permission checking based on query content

🧪 Testing & Validation

Model Compatibility Test

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:

  • ✅ Right tool chosen? (compute_account_health for analysis, run_sql for queries)
  • ✅ Args valid? (proper segment filtering, reasonable limits)
  • ✅ Result shape? (structured health scores, actionable insights)
  • ✅ Authentication? (role-based access control working)

Demo Workflows

  1. Schema Exploration: "What data is available?" (via schema resource)
  2. Customer Health Analysis: "Show me at-risk customers" (compute_account_health)
  3. Database Queries: "Show me recent high-value orders" (run_sql)
  4. Multi-system Integration: "Combine order data with support tickets" (workflow tool)
  5. Guardrails Test: "Try to delete users" (safely fails with helpful error)
  6. Authentication Test: Different access levels (admin/user/readonly)

📊 Best Practices Demonstrated

✅ Tool Design

  • Capability-oriented: run_sql (workflow) vs getUserById (endpoint)
  • Self-documenting: Descriptions include examples
  • Composable: One general tool > many specific ones

✅ Safety & Reliability

  • Read-only default: Only SELECT operations allowed
  • Implicit limits: Auto-add LIMIT clauses
  • Structured errors: "Only SELECT queries allowed" (teaches the model)

✅ Model Compatibility

  • Cross-model testing: GPT-4, GPT-3.5, etc.
  • Consistent behavior: Same tools, same args, same output shape
  • Graceful degradation: Weaker models still succeed

✅ Exploration & Discovery

  • Resource-driven: Schema exposed via MCP resources
  • Progressive disclosure: Start simple, add complexity
  • Documentation: Examples and usage notes included

🎮 Try It Yourself

Extend the Demo

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 }[]
  },
});

Connect to Your Own Database

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

Test with More Models

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
];

🎯 Key Takeaways

  1. Tools are workflows, not APIs - Design for capabilities
  2. Fewer is better - 2 general tools > 10 specific ones
  3. Safety first - Guardrails built into every tool
  4. Test compatibility - Multiple models, same behavior
  5. Enable exploration - Resources help discovery

📚 Resources

🤝 Contributing

Found an issue or want to improve the workshop?

  • Report bugs via GitHub issues
  • Submit improvements via pull requests
  • Share your workshop experiences

Built with ❤️ using Mastra - The TypeScript AI Framework

About

Workshop for creating MCP Servers using best practices

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published