google-search-console-mcp-server
Version:
Model Context Protocol server for Google Search Console API - integrate with Claude Code and Claude Desktop
177 lines • 7.42 kB
JavaScript
/**
* Compare Periods Tool
* Compare search performance between two time periods
*/
import { google } from 'googleapis';
import { handleApiError, retryWithBackoff, validateSiteUrl, validateDateRange } from '../utils/error-handler.js';
export const name = 'compare_periods';
export const description = 'Compare search performance metrics between two time periods (e.g., this week vs last week)';
export const inputSchema = {
type: 'object',
properties: {
siteUrl: {
type: 'string',
description: 'The site URL (e.g., "https://example.com/")',
},
currentStartDate: {
type: 'string',
description: 'Current period start date (YYYY-MM-DD)',
},
currentEndDate: {
type: 'string',
description: 'Current period end date (YYYY-MM-DD)',
},
previousStartDate: {
type: 'string',
description: 'Previous period start date (YYYY-MM-DD)',
},
previousEndDate: {
type: 'string',
description: 'Previous period end date (YYYY-MM-DD)',
},
dimensions: {
type: 'array',
items: {
type: 'string',
enum: ['query', 'page', 'country', 'device', 'searchAppearance'],
},
description: 'Dimensions to group results by (e.g., ["query"])',
},
rowLimit: {
type: 'number',
description: 'Maximum number of rows to return (default: 100, max: 25000)',
default: 100,
},
},
required: ['siteUrl', 'currentStartDate', 'currentEndDate', 'previousStartDate', 'previousEndDate'],
};
/**
* Handler for comparing performance between two periods
* Retrieves data for both periods and calculates change percentages
*/
export async function handler(args, authClient) {
const searchconsole = google.searchconsole({ version: 'v1', auth: authClient });
const { siteUrl, currentStartDate, currentEndDate, previousStartDate, previousEndDate, dimensions = [], rowLimit = 100, } = args;
// Validate inputs
validateSiteUrl(siteUrl);
validateDateRange(currentStartDate, currentEndDate);
validateDateRange(previousStartDate, previousEndDate);
if (rowLimit < 1 || rowLimit > 25000) {
throw new Error('rowLimit must be between 1 and 25000');
}
try {
// Fetch data for both periods in parallel
const [currentResponse, previousResponse] = await Promise.all([
retryWithBackoff(async () => {
return await searchconsole.searchanalytics.query({
siteUrl,
requestBody: {
startDate: currentStartDate,
endDate: currentEndDate,
dimensions,
rowLimit,
},
});
}),
retryWithBackoff(async () => {
return await searchconsole.searchanalytics.query({
siteUrl,
requestBody: {
startDate: previousStartDate,
endDate: previousEndDate,
dimensions,
rowLimit,
},
});
}),
]);
const currentRows = currentResponse.data.rows || [];
const previousRows = previousResponse.data.rows || [];
// Create a map of previous period data for easy lookup
const previousMap = new Map();
previousRows.forEach((row) => {
const key = row.keys?.join('|') || 'total';
previousMap.set(key, row);
});
// Calculate comparisons
const comparisons = currentRows.map((currentRow) => {
const key = currentRow.keys?.join('|') || 'total';
const previousRow = previousMap.get(key);
const calculateChange = (current, previous) => {
const change = current - previous;
const changePercent = previous > 0 ? (change / previous) * 100 : 0;
return {
current,
previous,
change,
changePercent: Math.round(changePercent * 100) / 100, // Round to 2 decimal places
};
};
return {
keys: currentRow.keys,
clicks: calculateChange(currentRow.clicks || 0, previousRow?.clicks || 0),
impressions: calculateChange(currentRow.impressions || 0, previousRow?.impressions || 0),
ctr: calculateChange(currentRow.ctr || 0, previousRow?.ctr || 0),
position: calculateChange(currentRow.position || 0, previousRow?.position || 0),
};
});
// Calculate overall totals
const currentTotals = {
clicks: currentRows.reduce((sum, row) => sum + (row.clicks || 0), 0),
impressions: currentRows.reduce((sum, row) => sum + (row.impressions || 0), 0),
ctr: currentRows.length > 0
? currentRows.reduce((sum, row) => sum + (row.ctr || 0), 0) / currentRows.length
: 0,
position: currentRows.length > 0
? currentRows.reduce((sum, row) => sum + (row.position || 0), 0) / currentRows.length
: 0,
};
const previousTotals = {
clicks: previousRows.reduce((sum, row) => sum + (row.clicks || 0), 0),
impressions: previousRows.reduce((sum, row) => sum + (row.impressions || 0), 0),
ctr: previousRows.length > 0
? previousRows.reduce((sum, row) => sum + (row.ctr || 0), 0) / previousRows.length
: 0,
position: previousRows.length > 0
? previousRows.reduce((sum, row) => sum + (row.position || 0), 0) / previousRows.length
: 0,
};
const calculateChange = (current, previous) => {
const change = current - previous;
const changePercent = previous > 0 ? (change / previous) * 100 : 0;
return {
current,
previous,
change,
changePercent: Math.round(changePercent * 100) / 100,
};
};
const result = {
periods: {
current: { startDate: currentStartDate, endDate: currentEndDate },
previous: { startDate: previousStartDate, endDate: previousEndDate },
},
totals: {
clicks: calculateChange(currentTotals.clicks, previousTotals.clicks),
impressions: calculateChange(currentTotals.impressions, previousTotals.impressions),
ctr: calculateChange(currentTotals.ctr, previousTotals.ctr),
position: calculateChange(currentTotals.position, previousTotals.position),
},
rows: comparisons,
dimensions: dimensions,
totalRows: comparisons.length,
};
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
catch (error) {
handleApiError(error, 'compare periods');
}
}
//# sourceMappingURL=compare-periods.js.map