Skip to main content
Glama

get_analytics

Generate analytics reports on contact submissions to analyze summary statistics, purpose breakdown, daily trends, and activity patterns for data-driven insights.

Instructions

Generate analytics reports on contact submissions including summary statistics, purpose breakdown, daily trends, and recent activity patterns

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
report_typeNoType of analytics report to generatesummary
date_fromNoStart date for custom period (YYYY-MM-DD format)
date_toNoEnd date for custom period (YYYY-MM-DD format)

Implementation Reference

  • The core handler function for the 'get_analytics' tool. It processes input parameters (report_type, date_from, date_to) and executes database queries using Drizzle ORM to generate various analytics reports (summary, purpose breakdown, daily trends, recent activity, custom period). Returns structured content blocks with formatted text.
    async ({ report_type = "summary", date_from, date_to }) => { try { switch (report_type) { case "summary": { const purposeBreakdown = await db .select({ purpose: contacts.purpose, count: count(contacts.id), }) .from(contacts) .groupBy(contacts.purpose); const total = purposeBreakdown.reduce((sum, item) => sum + item.count, 0); const thirtyDaysAgo = Math.floor( (Date.now() - 30 * 24 * 60 * 60 * 1000) / 1000, ); const recentCount = await db .select({ count: count(contacts.id) }) .from(contacts) .where(gte(contacts.createdAt, new Date(thirtyDaysAgo * 1000))); const breakdown = purposeBreakdown .map((item) => `${item.purpose.replace("_", " ")}: ${item.count}`) .join(", "); return { content: [ { type: "text", text: `Contact Analytics Summary Total Contacts: ${total} Recent (30 days): ${recentCount[0]?.count || 0} Purpose Breakdown: ${breakdown} Status: ${total > 0 ? "Active contact system" : "No contacts yet"}`, }, ], }; } case "purpose_breakdown": { const result = await db .select({ purpose: contacts.purpose, count: count(contacts.id), }) .from(contacts) .groupBy(contacts.purpose); const total = result.reduce((sum, item) => sum + item.count, 0); const breakdown = result .map((item) => { const percentage = total > 0 ? ((item.count / total) * 100).toFixed(1) : 0; return `${item.purpose.replace("_", " ")} Count: ${item.count} Percentage: ${percentage}%`; }) .join("\n\n"); return { content: [ { type: "text", text: `Contact Purpose Breakdown Total Contacts: ${total} ${breakdown}`, }, ], }; } case "daily_trends": { const result = await db .select({ date: sql<string>`strftime('%Y-%m-%d', datetime(${contacts.createdAt}, 'unixepoch'))`, daily_total: count(contacts.id), }) .from(contacts) .groupBy( sql`strftime('%Y-%m-%d', datetime(${contacts.createdAt}, 'unixepoch'))`, ) .orderBy( sql`strftime('%Y-%m-%d', datetime(${contacts.createdAt}, 'unixepoch')) DESC`, ) .limit(30); const chart = result .map((item) => { const date = new Date(item.date!).toLocaleDateString(); return `${date}: ${item.daily_total} contacts`; }) .join("\n"); const totalRecent = result.reduce((sum, item) => sum + item.daily_total, 0); const avgDaily = (totalRecent / result.length).toFixed(1); const maxValue = Math.max(...result.map((item) => item.daily_total)); return { content: [ { type: "text", text: `Daily Contact Trends (Last 30 Days) ${chart} Statistics - Total (30 days): ${totalRecent} - Daily Average: ${avgDaily} - Peak Day: ${maxValue} contacts - Most Recent: ${result[0]?.daily_total || 0} contacts`, }, ], }; } case "recent_activity": { const sevenDaysAgo = Math.floor( (Date.now() - 7 * 24 * 60 * 60 * 1000) / 1000, ); const result = await db .select({ purpose: contacts.purpose, total: count(contacts.id), last_submission: sql<string>`max(${contacts.createdAt})`, }) .from(contacts) .where(gte(contacts.createdAt, new Date(sevenDaysAgo * 1000))) .groupBy(contacts.purpose); const total = result.reduce((sum, item) => sum + item.total, 0); const activity = result .map((item) => { const lastDate = new Date( item.last_submission!, ).toLocaleDateString(); return `${item.purpose.replace("_", " ")} - Submissions: ${item.total} - Last activity: ${lastDate}`; }) .join("\n\n"); return { content: [ { type: "text", text: `Recent Activity (Last 7 Days) Total Submissions: ${total} ${activity || "No recent activity"} Status: ${total > 0 ? "Active" : "Quiet period"}`, }, ], }; } case "custom_period": { if (!date_from || !date_to) { return { content: [ { type: "text", text: `Missing Date Range For custom period analytics, please provide both date_from and date_to parameters in YYYY-MM-DD format. Example: date_from: "2024-01-01", date_to: "2024-01-31"`, }, ], }; } const startDate = new Date(date_from); const endDate = new Date(date_to); if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime())) { return { content: [ { type: "text", text: "Invalid date format. Please use YYYY-MM-DD format.", }, ], }; } const result = await db .select({ purpose: contacts.purpose, count: count(contacts.id), }) .from(contacts) .where( sql`${contacts.createdAt} >= ${Math.floor(startDate.getTime() / 1000)} AND ${contacts.createdAt} <= ${Math.floor(endDate.getTime() / 1000)}`, ) .groupBy(contacts.purpose); const total = result.reduce((sum, item) => sum + item.count, 0); const breakdown = result .map((item) => `${item.purpose.replace("_", " ")}: ${item.count}`) .join("\n"); return { content: [ { type: "text", text: `Custom Period Analytics (${date_from} to ${date_to}) Total Contacts: ${total} Purpose Breakdown: ${breakdown || "No contacts in this period"}`, }, ], }; } default: return { content: [ { type: "text", text: `Invalid Report Type Supported report types: - summary - purpose_breakdown - daily_trends - recent_activity - custom_period`, }, ], }; } } catch (_error) { // Log error securely without exposing details return { content: [ { type: "text", text: `Unexpected Error An error occurred while generating analytics. Please try again later.`, }, ], }; } },
  • Zod-based input schema definitions for the tool parameters: report_type (enum of report types), date_from and date_to (optional strings for custom date ranges).
    const reportTypeSchema = z .enum(["summary", "purpose_breakdown", "daily_trends", "recent_activity", "custom_period"]) .default("summary") as any; const dateFromSchema = z.string().optional() as any; const dateToSchema = z.string().optional() as any; import { getDb } from "../database/index"; import { contacts } from "../database/schema"; /** * Register the contact-analytics MCP tool for insights and statistics * Analytics data is written to Cloudflare Analytics Engine and computed from D1 contacts */ export function registerGetAnalyticsTool(server: McpServer, env: Env) { const db = getDb(env.DB); server.registerTool( "get_analytics", { title: "Get Analytics", description: "Generate analytics reports on contact submissions including summary statistics, purpose breakdown, daily trends, and recent activity patterns", inputSchema: { report_type: reportTypeSchema.describe("Type of analytics report to generate"), date_from: dateFromSchema.describe( "Start date for custom period (YYYY-MM-DD format)", ), date_to: dateToSchema.describe("End date for custom period (YYYY-MM-DD format)"), },
  • The registerGetAnalyticsTool function that calls server.registerTool to define and register the 'get_analytics' tool with its schema and handler.
    export function registerGetAnalyticsTool(server: McpServer, env: Env) { const db = getDb(env.DB); server.registerTool( "get_analytics", { title: "Get Analytics", description: "Generate analytics reports on contact submissions including summary statistics, purpose breakdown, daily trends, and recent activity patterns", inputSchema: { report_type: reportTypeSchema.describe("Type of analytics report to generate"), date_from: dateFromSchema.describe( "Start date for custom period (YYYY-MM-DD format)", ), date_to: dateToSchema.describe("End date for custom period (YYYY-MM-DD format)"), }, }, async ({ report_type = "summary", date_from, date_to }) => { try { switch (report_type) { case "summary": { const purposeBreakdown = await db .select({ purpose: contacts.purpose, count: count(contacts.id), }) .from(contacts) .groupBy(contacts.purpose); const total = purposeBreakdown.reduce((sum, item) => sum + item.count, 0); const thirtyDaysAgo = Math.floor( (Date.now() - 30 * 24 * 60 * 60 * 1000) / 1000, ); const recentCount = await db .select({ count: count(contacts.id) }) .from(contacts) .where(gte(contacts.createdAt, new Date(thirtyDaysAgo * 1000))); const breakdown = purposeBreakdown .map((item) => `${item.purpose.replace("_", " ")}: ${item.count}`) .join(", "); return { content: [ { type: "text", text: `Contact Analytics Summary Total Contacts: ${total} Recent (30 days): ${recentCount[0]?.count || 0} Purpose Breakdown: ${breakdown} Status: ${total > 0 ? "Active contact system" : "No contacts yet"}`, }, ], }; } case "purpose_breakdown": { const result = await db .select({ purpose: contacts.purpose, count: count(contacts.id), }) .from(contacts) .groupBy(contacts.purpose); const total = result.reduce((sum, item) => sum + item.count, 0); const breakdown = result .map((item) => { const percentage = total > 0 ? ((item.count / total) * 100).toFixed(1) : 0; return `${item.purpose.replace("_", " ")} Count: ${item.count} Percentage: ${percentage}%`; }) .join("\n\n"); return { content: [ { type: "text", text: `Contact Purpose Breakdown Total Contacts: ${total} ${breakdown}`, }, ], }; } case "daily_trends": { const result = await db .select({ date: sql<string>`strftime('%Y-%m-%d', datetime(${contacts.createdAt}, 'unixepoch'))`, daily_total: count(contacts.id), }) .from(contacts) .groupBy( sql`strftime('%Y-%m-%d', datetime(${contacts.createdAt}, 'unixepoch'))`, ) .orderBy( sql`strftime('%Y-%m-%d', datetime(${contacts.createdAt}, 'unixepoch')) DESC`, ) .limit(30); const chart = result .map((item) => { const date = new Date(item.date!).toLocaleDateString(); return `${date}: ${item.daily_total} contacts`; }) .join("\n"); const totalRecent = result.reduce((sum, item) => sum + item.daily_total, 0); const avgDaily = (totalRecent / result.length).toFixed(1); const maxValue = Math.max(...result.map((item) => item.daily_total)); return { content: [ { type: "text", text: `Daily Contact Trends (Last 30 Days) ${chart} Statistics - Total (30 days): ${totalRecent} - Daily Average: ${avgDaily} - Peak Day: ${maxValue} contacts - Most Recent: ${result[0]?.daily_total || 0} contacts`, }, ], }; } case "recent_activity": { const sevenDaysAgo = Math.floor( (Date.now() - 7 * 24 * 60 * 60 * 1000) / 1000, ); const result = await db .select({ purpose: contacts.purpose, total: count(contacts.id), last_submission: sql<string>`max(${contacts.createdAt})`, }) .from(contacts) .where(gte(contacts.createdAt, new Date(sevenDaysAgo * 1000))) .groupBy(contacts.purpose); const total = result.reduce((sum, item) => sum + item.total, 0); const activity = result .map((item) => { const lastDate = new Date( item.last_submission!, ).toLocaleDateString(); return `${item.purpose.replace("_", " ")} - Submissions: ${item.total} - Last activity: ${lastDate}`; }) .join("\n\n"); return { content: [ { type: "text", text: `Recent Activity (Last 7 Days) Total Submissions: ${total} ${activity || "No recent activity"} Status: ${total > 0 ? "Active" : "Quiet period"}`, }, ], }; } case "custom_period": { if (!date_from || !date_to) { return { content: [ { type: "text", text: `Missing Date Range For custom period analytics, please provide both date_from and date_to parameters in YYYY-MM-DD format. Example: date_from: "2024-01-01", date_to: "2024-01-31"`, }, ], }; } const startDate = new Date(date_from); const endDate = new Date(date_to); if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime())) { return { content: [ { type: "text", text: "Invalid date format. Please use YYYY-MM-DD format.", }, ], }; } const result = await db .select({ purpose: contacts.purpose, count: count(contacts.id), }) .from(contacts) .where( sql`${contacts.createdAt} >= ${Math.floor(startDate.getTime() / 1000)} AND ${contacts.createdAt} <= ${Math.floor(endDate.getTime() / 1000)}`, ) .groupBy(contacts.purpose); const total = result.reduce((sum, item) => sum + item.count, 0); const breakdown = result .map((item) => `${item.purpose.replace("_", " ")}: ${item.count}`) .join("\n"); return { content: [ { type: "text", text: `Custom Period Analytics (${date_from} to ${date_to}) Total Contacts: ${total} Purpose Breakdown: ${breakdown || "No contacts in this period"}`, }, ], }; } default: return { content: [ { type: "text", text: `Invalid Report Type Supported report types: - summary - purpose_breakdown - daily_trends - recent_activity - custom_period`, }, ], }; } } catch (_error) { // Log error securely without exposing details return { content: [ { type: "text", text: `Unexpected Error An error occurred while generating analytics. Please try again later.`, }, ], }; } }, ); }
  • In the registerAllTools function, imports and invokes registerGetAnalyticsTool to register the tool as part of the overall MCP tools initialization.
    import { registerGetAnalyticsTool } from "./contact-analytics"; /** * Register all MCP tools with the server */ export function registerAllTools(server: McpServer, env: Env) { logger.info("init", "Registering MCP tools"); // Content tools registerGitHubActivityTool(server); logger.tool("github_activity", "registered"); registerGetBlogPostContentTool(server); logger.tool("get_blog_post_content", "registered"); // Web tools registerWebSearchTool(server); logger.tool("web-search", "registered"); registerWebFetchTool(server); logger.tool("web-fetch", "registered"); // Interaction tools registerSendMessageTool(server, env); logger.tool("send_message", "registered"); registerHireMeTool(server, env); logger.tool("hire_me", "registered"); registerSayHiTool(server); logger.tool("say_hi", "registered"); // Management tools registerGetAnalyticsTool(server, env); logger.tool("get_analytics", "registered");

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/duyet/duyet-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server