get_analytics
Generate detailed analytics reports on contact submissions, including summary statistics, purpose breakdown, daily trends, and recent activity patterns. Customize reports by selecting specific report types and date ranges.
Instructions
Generate analytics reports on contact submissions including summary statistics, purpose breakdown, daily trends, and recent activity patterns
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| date_from | No | Start date for custom period (YYYY-MM-DD format) | |
| date_to | No | End date for custom period (YYYY-MM-DD format) | |
| report_type | No | Type of analytics report to generate | summary |
Implementation Reference
- src/tools/contact-analytics.ts:32-291 (handler)Main handler logic for get_analytics tool. Handles multiple report types by querying contacts database with Drizzle ORM, computing statistics, and returning formatted text responses.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.`, }, ], }; } },
- src/tools/contact-analytics.ts:6-30 (schema)Zod-based input schema definition for the tool parameters: report_type (enum), date_from (optional string), date_to (optional string). Used in server.registerTool.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)"), },
- src/tools/contact-analytics.ts:20-292 (registration)Core registration of the 'get_analytics' tool using server.registerTool, specifying name, metadata, schema, and inline handler function.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.`, }, ], }; } }, );
- src/tools/index.ts:47-48 (registration)Invocation of registerGetAnalyticsTool during overall tool registration in registerAllTools, confirming the tool is registered in the MCP server.registerGetAnalyticsTool(server, env); logger.tool("get_analytics", "registered");