Social Listening MCP Server
by fred-em
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
interface ToolCall {
name: string;
arguments: Record<string, any>;
}
interface DateRange {
start_date: string;
end_date: string;
}
// Patterns for date expressions
const DATE_PATTERNS = [
{
pattern: /last (\d+) (day|days|week|weeks|month|months)/i,
handler: (matches: RegExpMatchArray): DateRange => {
const amount = parseInt(matches[1]);
const unit = matches[2].toLowerCase();
const end = new Date();
const start = new Date();
switch (unit) {
case 'day':
case 'days':
start.setDate(start.getDate() - amount);
break;
case 'week':
case 'weeks':
start.setDate(start.getDate() - (amount * 7));
break;
case 'month':
case 'months':
start.setMonth(start.getMonth() - amount);
break;
}
return {
start_date: start.toISOString().split('T')[0],
end_date: end.toISOString().split('T')[0]
};
}
},
{
pattern: /last (week|month|year)/i,
handler: (matches: RegExpMatchArray): DateRange => {
const unit = matches[1].toLowerCase();
const end = new Date();
const start = new Date();
switch (unit) {
case 'week':
start.setDate(start.getDate() - 7);
break;
case 'month':
start.setMonth(start.getMonth() - 1);
break;
case 'year':
start.setFullYear(start.getFullYear() - 1);
break;
}
return {
start_date: start.toISOString().split('T')[0],
end_date: end.toISOString().split('T')[0]
};
}
},
{
pattern: /month of (\w+ \d{4})/i,
handler: (matches: RegExpMatchArray): DateRange => {
const date = new Date(matches[1]);
if (isNaN(date.getTime())) {
throw new Error('Invalid date format');
}
const start = new Date(date.getFullYear(), date.getMonth(), 1);
const end = new Date(date.getFullYear(), date.getMonth() + 1, 0);
return {
start_date: start.toISOString().split('T')[0],
end_date: end.toISOString().split('T')[0]
};
}
}
];
// Extract confidence score from text
function extractConfidence(text: string): number | undefined {
const matches = text.match(/confidence (?:score )?(?:of )?(\d+(?:\.\d+)?)/i);
if (matches) {
const score = parseFloat(matches[1]);
if (score >= 0 && score <= 1) {
return score;
}
}
return undefined;
}
// Extract categories from text
function extractCategories(text: string): string[] {
const categories = [];
const categoryMap = {
spam: ['spam', 'junk', 'unwanted'],
promotional: ['promotional', 'promo', 'marketing', 'advertisement', 'ads'],
support: ['support', 'help', 'assistance'],
feedback: ['feedback', 'review', 'opinion'],
inquiry: ['inquiry', 'question', 'ask']
};
for (const [category, keywords] of Object.entries(categoryMap)) {
if (keywords.some(keyword => text.toLowerCase().includes(keyword))) {
categories.push(category);
}
}
return categories;
}
// Extract date range from text
function extractDateRange(text: string): DateRange {
for (const { pattern, handler } of DATE_PATTERNS) {
const matches = text.match(pattern);
if (matches) {
return handler(matches);
}
}
// Default to last 30 days if no date range found
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 30);
return {
start_date: start.toISOString().split('T')[0],
end_date: end.toISOString().split('T')[0]
};
}
// Extract limit from text
function extractLimit(text: string): number | undefined {
const matches = text.match(/(?:limit|top|first) (\d+)/i);
if (matches) {
return parseInt(matches[1]);
}
return undefined;
}
// Main function to process natural language prompts
export function processNaturalLanguage(prompt: string): ToolCall[] {
const text = prompt.toLowerCase();
const calls: ToolCall[] = [];
// Check for AI filter configuration
if (text.includes('configure ai') || text.includes('set up ai') || text.includes('update ai settings')) {
const confidence = extractConfidence(text) || 0.7;
const categories = extractCategories(text);
calls.push({
name: 'configure_ai_filter',
arguments: {
enabled: true,
min_confidence: confidence,
categories
}
});
return calls;
}
// Check for webhook configuration
if (text.includes('webhook') && (text.includes('configure') || text.includes('set up') || text.includes('update'))) {
throw new McpError(
ErrorCode.InvalidParams,
'Webhook configuration requires explicit endpoint URL and secret token parameters'
);
}
// Check for webhook status
if (text.includes('webhook') && text.includes('status')) {
calls.push({
name: 'get_webhook_status',
arguments: {}
});
return calls;
}
// Check for backfill request
if (text.includes('backfill')) {
const matches = text.match(/month of (\w+ \d{4})/i);
if (matches) {
const date = new Date(matches[1]);
if (!isNaN(date.getTime())) {
calls.push({
name: 'backfill_month',
arguments: {
year: date.getFullYear(),
month: date.getMonth() + 1
}
});
return calls;
}
}
}
// Check for sync request
if (text.includes('sync') || text.includes('update') || text.includes('get new')) {
calls.push({
name: 'sync_latest',
arguments: {}
});
return calls;
}
// Check for AI filtered mentions
if (text.includes('ai') || text.includes('filter')) {
const dateRange = extractDateRange(text);
const confidence = extractConfidence(text);
const categories = extractCategories(text);
const limit = extractLimit(text);
const args: Record<string, any> = {
...dateRange
};
if (confidence !== undefined) {
args.min_confidence = confidence;
}
if (categories.length > 0) {
args.categories = categories;
}
if (limit !== undefined) {
args.limit = limit;
}
calls.push({
name: 'get_ai_filtered_mentions',
arguments: args
});
return calls;
}
// Check for trend analysis
if (text.includes('trend') || text.includes('analyze')) {
const dateRange = extractDateRange(text);
const args: Record<string, any> = {
...dateRange
};
// Determine grouping
if (text.includes('week')) {
args.group_by = 'week';
} else if (text.includes('month')) {
args.group_by = 'month';
} else {
args.group_by = 'day';
}
calls.push({
name: 'analyze_trends',
arguments: args
});
return calls;
}
// Check for top sources
if (text.includes('top') || text.includes('source')) {
const dateRange = extractDateRange(text);
const limit = extractLimit(text);
const args: Record<string, any> = {
...dateRange
};
if (limit !== undefined) {
args.limit = limit;
}
calls.push({
name: 'get_top_sources',
arguments: args
});
return calls;
}
throw new McpError(
ErrorCode.InvalidParams,
'Could not determine the intended action from the prompt. Please try rephrasing your request.'
);
}