feat: add MoveToJunk, AnalyzeSpam, and ScanForSpam MCP tools

MoveToJunk moves emails to Junk by subject/date. AnalyzeSpam returns
a detailed spam report with score, red flags, and sender/content
analysis. ScanForSpam batch-scans recent emails and ranks by spam
likelihood.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 11:06:43 -05:00
parent 51858cbd01
commit 489cb8f657

View File

@@ -5,6 +5,7 @@ using System.Text;
using OutlookApp = NetOffice.OutlookApi.Application;
using NetOffice.OutlookApi;
using NetOffice.OutlookApi.Enums;
using EmailSearch.SpamDetection;
[McpServerToolType]
public class EmailSearchTools
@@ -75,6 +76,41 @@ public class EmailSearchTools
}
}
[McpServerTool, Description("Move an email to the Junk folder by subject and date. Use after SearchEmails to identify the email to move.")]
public static string MoveToJunk(
[Description("Exact or partial subject line to match")] string subject,
[Description("Date of the email (supports: yyyy-MM-dd, MM/dd/yyyy, dd/MM/yyyy)")] string date,
[Description("Outlook folder to search for the email: Inbox, SentMail, Drafts, All, or any custom folder name (default Inbox)")] string folder = "Inbox")
{
try
{
if (!TryParseDate(date, out var targetDate))
return $"Invalid date format '{date}'. Supported formats: yyyy-MM-dd, MM/dd/yyyy, dd/MM/yyyy";
using var outlookApp = new OutlookApp();
var ns = outlookApp.GetNamespace("MAPI");
var foldersToSearch = GetFoldersToSearch(ns, folder);
var junkFolder = ns.GetDefaultFolder(OlDefaultFolders.olFolderJunk);
foreach (var mailFolder in foldersToSearch)
{
var mail = FindEmail(mailFolder, subject, targetDate);
if (mail != null)
{
var emailSubject = mail.Subject;
mail.Move(junkFolder);
return $"Successfully moved email '{emailSubject}' to Junk folder.";
}
}
return "Email not found with the specified subject and date.";
}
catch (System.Exception ex)
{
return $"Error moving email to junk: {ex.Message}";
}
}
[McpServerTool, Description("Read the full body of a specific email by subject and date. Use after SearchEmails to get complete email content.")]
public static string ReadEmail(
[Description("Exact or partial subject line to match")] string subject,
@@ -105,6 +141,198 @@ public class EmailSearchTools
}
}
[McpServerTool, Description("Analyze a specific email for spam indicators. Returns spam score (0.0-1.0), spam likelihood, and detected red flags.")]
public static string AnalyzeSpam(
[Description("Exact or partial subject line to match")] string subject,
[Description("Date of the email (supports: yyyy-MM-dd, MM/dd/yyyy, dd/MM/yyyy)")] string date,
[Description("Outlook folder: Inbox, SentMail, Drafts, DeletedItems, Junk, All, or any custom folder name (default All)")] string folder = "All")
{
try
{
if (!TryParseDate(date, out var targetDate))
return $"Invalid date format '{date}'. Supported formats: yyyy-MM-dd, MM/dd/yyyy, dd/MM/yyyy";
using var outlookApp = new OutlookApp();
var ns = outlookApp.GetNamespace("MAPI");
var foldersToSearch = GetFoldersToSearch(ns, folder);
foreach (var mailFolder in foldersToSearch)
{
var mail = FindEmail(mailFolder, subject, targetDate);
if (mail != null)
{
var detector = new SpamDetector();
var result = detector.Analyze(mail);
return FormatSpamAnalysis(mail, result);
}
}
return "Email not found with the specified subject and date.";
}
catch (System.Exception ex)
{
return $"Error analyzing email: {ex.Message}";
}
}
[McpServerTool, Description("Scan recent emails for spam and return a summary with spam scores. Useful for identifying potential spam in your inbox.")]
public static string ScanForSpam(
[Description("Number of days back to scan (default 7)")] int daysBack = 7,
[Description("Maximum number of emails to scan (default 50)")] int maxEmails = 50,
[Description("Minimum spam score to include in results (0.0-1.0, default 0.3)")] double minScore = 0.3,
[Description("Outlook folder to scan: Inbox, SentMail, Drafts, All, or custom folder name (default Inbox)")] string folder = "Inbox")
{
try
{
using var outlookApp = new OutlookApp();
var ns = outlookApp.GetNamespace("MAPI");
var foldersToSearch = GetFoldersToSearch(ns, folder);
var cutoffDate = DateTime.Now.AddDays(-daysBack);
var detector = new SpamDetector();
var results = new List<(EmailResult email, SpamAnalysisResult spam)>();
foreach (var mailFolder in foldersToSearch)
{
try
{
var items = mailFolder.Items;
items.Sort("[ReceivedTime]", true);
var filter = $"[ReceivedTime] >= '{cutoffDate:MM/dd/yyyy}'";
var filteredItems = items.Restrict(filter);
foreach (var item in filteredItems)
{
if (results.Count >= maxEmails)
break;
if (item is MailItem mail)
{
var spamResult = detector.Analyze(mail);
if (spamResult.FinalScore >= minScore)
{
var emailResult = EmailResult.FromMailItem(mail, mailFolder.Name);
results.Add((emailResult, spamResult));
}
}
}
}
catch { }
if (results.Count >= maxEmails)
break;
}
if (results.Count == 0)
return $"No emails with spam score >= {minScore:P0} found in the last {daysBack} days.";
// Sort by spam score descending
results = results.OrderByDescending(r => r.spam.FinalScore).ToList();
return FormatSpamScanResults(results, daysBack, minScore);
}
catch (System.Exception ex)
{
return $"Error scanning for spam: {ex.Message}";
}
}
private static string FormatSpamAnalysis(MailItem mail, SpamAnalysisResult result)
{
var output = new StringBuilder();
output.AppendLine("=== SPAM ANALYSIS REPORT ===");
output.AppendLine();
output.AppendLine($"Subject: {mail.Subject}");
output.AppendLine($"From: {mail.SenderName} <{mail.SenderEmailAddress}>");
output.AppendLine($"Date: {mail.ReceivedTime:yyyy-MM-dd HH:mm}");
output.AppendLine();
output.AppendLine("--- SPAM SCORE ---");
output.AppendLine($"Score: {result.FinalScore:P0}");
output.AppendLine($"Likelihood: {result.SpamLikelihood}");
output.AppendLine($"Predicted Spam: {(result.PredictedSpam ? "YES" : "No")}");
output.AppendLine();
if (result.RedFlags.Count > 0)
{
output.AppendLine("--- RED FLAGS DETECTED ---");
foreach (var flag in result.RedFlags)
{
output.AppendLine($" - {flag}");
}
output.AppendLine();
}
if (result.Features != null)
{
output.AppendLine("--- SENDER ANALYSIS ---");
output.AppendLine($"Display Name: {result.Features.DisplayName}");
output.AppendLine($"Email Address: {result.Features.FromAddress}");
output.AppendLine($"Domain: {result.Features.FromDomain}");
output.AppendLine($"Free Email Provider: {(result.Features.FreeMailboxDomain ? "Yes" : "No")}");
output.AppendLine($"Known/Trusted Domain: {(!result.Features.UnknownDomain ? "Yes" : "No")}");
output.AppendLine($"Blocklisted: {(result.Features.IsBlocklisted ? "YES" : "No")}");
output.AppendLine();
output.AppendLine("--- AUTHENTICATION ---");
output.AppendLine($"SPF Failed: {(result.Features.SpfFail ? "YES" : "No")}");
output.AppendLine($"DKIM Failed: {(result.Features.DkimFail ? "YES" : "No")}");
output.AppendLine($"DMARC Failed: {(result.Features.DmarcFail ? "YES" : "No")}");
output.AppendLine($"Reply-To Mismatch: {(result.Features.ReplyToDomainMismatch ? "YES" : "No")}");
output.AppendLine();
output.AppendLine("--- CONTENT ANALYSIS ---");
output.AppendLine($"URLs Found: {result.Features.UrlCount}");
output.AppendLine($"Uses URL Shortener: {(result.Features.UsesShortener ? "YES" : "No")}");
output.AppendLine($"IP-based URL: {(result.Features.HasIpLink ? "YES" : "No")}");
output.AppendLine($"Suspicious TLD: {(result.Features.SuspiciousTld ? "YES" : "No")}");
output.AppendLine($"Has Attachments: {(result.Features.HasAttachment ? "Yes" : "No")}");
if (result.Features.HasAttachment)
output.AppendLine($"Attachment Risk: {result.Features.AttachmentRiskScore:P0}");
output.AppendLine($"Keyword Bait: {(result.Features.KeywordBait ? "YES" : "No")}");
output.AppendLine($"Has Tracking Pixel: {(result.Features.HasTrackingPixel ? "Yes" : "No")}");
}
return output.ToString();
}
private static string FormatSpamScanResults(List<(EmailResult email, SpamAnalysisResult spam)> results, int daysBack, double minScore)
{
var output = new StringBuilder();
output.AppendLine($"=== SPAM SCAN RESULTS ===");
output.AppendLine($"Scanned last {daysBack} days, showing {results.Count} email(s) with spam score >= {minScore:P0}");
output.AppendLine();
foreach (var (email, spam) in results)
{
var scoreBar = new string('#', (int)(spam.FinalScore * 10));
var emptyBar = new string('-', 10 - scoreBar.Length);
output.AppendLine($"[{scoreBar}{emptyBar}] {spam.FinalScore:P0} - {spam.SpamLikelihood}");
output.AppendLine($" Subject: {email.Subject}");
output.AppendLine($" From: {email.Sender}");
output.AppendLine($" Date: {email.ReceivedDate:yyyy-MM-dd HH:mm}");
output.AppendLine($" Folder: {email.Folder}");
if (spam.RedFlags.Count > 0)
{
var topFlags = spam.RedFlags.Take(3);
output.AppendLine($" Flags: {string.Join("; ", topFlags)}");
}
output.AppendLine();
}
var highSpam = results.Count(r => r.spam.FinalScore >= 0.7);
var mediumSpam = results.Count(r => r.spam.FinalScore >= 0.5 && r.spam.FinalScore < 0.7);
output.AppendLine("--- SUMMARY ---");
output.AppendLine($"High likelihood spam (>=70%): {highSpam}");
output.AppendLine($"Medium likelihood spam (50-69%): {mediumSpam}");
output.AppendLine($"Lower likelihood ({minScore:P0}-49%): {results.Count - highSpam - mediumSpam}");
return output.ToString();
}
private static List<MAPIFolder> GetFoldersToSearch(_NameSpace ns, string folder)
{
var folders = new List<MAPIFolder>();