jobsearch-mcp-server / src /tools /cover_letter_tool.py
daniielyan's picture
πŸ”¨ Implement core functionality for Job Search MCP Server, including user profile management, job search, cover letter generation, and Q&A response tools. Add configuration and service layers, and establish dependency management with uv. Introduce .gitignore and .python-version files for environment setup.
4fd18a2
"""Cover letter tool for generating personalized cover letters - letter.generate endpoint."""
from typing import Dict, Any
from ..services import LLMService, ProfileService
class CoverLetterTool:
"""Tool for generating personalized cover letters."""
def __init__(self):
self.llm_service = LLMService()
self.profile_service = ProfileService()
def generate(
self, user_id: str, job_description: str, tone: str = "professional"
) -> Dict[str, Any]:
"""
Generate a personalized cover letter using LLM.
This is the main letter.generate endpoint that calls an LLM to create
a short, personalized cover letter in any tone.
Args:
user_id: User identifier to access profile for personalization
job_description: The job posting description to tailor the letter to
tone: Tone of the cover letter
Options: "professional", "casual", "enthusiastic", "formal"
Default: "professional"
Returns:
Dict with generated cover letter and metadata:
{
"success": True,
"cover_letter": "Dear Hiring Manager,\n\nI am writing to express...",
"tone_used": "professional",
"word_count": 245,
"character_count": 1456,
"estimated_reading_time": "1 minute"
}
"""
try:
# Get user profile
profile = self.profile_service.get_profile(user_id)
if not profile:
return {
"success": False,
"message": "User profile not found. Please create a profile first.",
}
# Validate tone
valid_tones = ["professional", "casual", "enthusiastic", "formal"]
if tone not in valid_tones:
return {
"success": False,
"message": f"Invalid tone. Must be one of: {', '.join(valid_tones)}",
}
# Validate job description
if not job_description or len(job_description.strip()) < 50:
return {
"success": False,
"message": "Job description must be at least 50 characters long",
}
# Generate cover letter
result = self.llm_service.generate_cover_letter(
profile, job_description, tone
)
# Add additional metadata if successful
if result.get("success") and result.get("cover_letter"):
word_count = result.get("word_count", 0)
result["estimated_reading_time"] = self._estimate_reading_time(
word_count
)
result["tips"] = self._get_cover_letter_tips(tone)
return result
except Exception as e:
return {
"success": False,
"message": f"Error generating cover letter: {str(e)}",
}
def generate_multiple_tones(
self, user_id: str, job_description: str, tones: list[str] = None
) -> Dict[str, Any]:
"""
Generate cover letters in multiple tones for comparison.
Args:
user_id: User identifier
job_description: Job posting description
tones: List of tones to generate (default: ["professional", "enthusiastic"])
Returns:
Dict with multiple cover letters in different tones
"""
if tones is None:
tones = ["professional", "enthusiastic"]
results = {}
errors = []
for tone in tones:
result = self.generate(user_id, job_description, tone)
if result.get("success"):
results[tone] = result
else:
errors.append(f"{tone}: {result.get('message', 'Unknown error')}")
if results:
return {
"success": True,
"cover_letters": results,
"tones_generated": list(results.keys()),
"errors": errors if errors else None,
}
else:
return {
"success": False,
"message": "Failed to generate any cover letters",
"errors": errors,
}
def customize_for_company(
self,
user_id: str,
job_description: str,
company_info: str,
tone: str = "professional",
) -> Dict[str, Any]:
"""
Generate a cover letter with additional company-specific customization.
Args:
user_id: User identifier
job_description: Job posting description
company_info: Additional information about the company
tone: Tone for the cover letter
Returns:
Dict with customized cover letter
"""
try:
# Enhance job description with company info
enhanced_description = (
f"{job_description}\n\nCompany Information:\n{company_info}"
)
# Generate the cover letter
result = self.generate(user_id, enhanced_description, tone)
if result.get("success"):
result["customization"] = "Company-specific information included"
return result
except Exception as e:
return {
"success": False,
"message": f"Error generating customized cover letter: {str(e)}",
}
def _estimate_reading_time(self, word_count: int) -> str:
"""Estimate reading time based on word count (average 200 words per minute)."""
if word_count == 0:
return "0 minutes"
minutes = max(1, round(word_count / 200))
if minutes == 1:
return "1 minute"
else:
return f"{minutes} minutes"
def _get_cover_letter_tips(self, tone: str) -> list[str]:
"""Get tone-specific tips for cover letters."""
tips_by_tone = {
"professional": [
"Keep it concise and focused on achievements",
"Use formal language and proper business format",
"Highlight quantifiable results when possible",
],
"casual": [
"Show personality while remaining respectful",
"Use conversational language but avoid slang",
"Focus on cultural fit and team collaboration",
],
"enthusiastic": [
"Express genuine excitement for the role",
"Use energetic language and positive adjectives",
"Show passion for the company's mission",
],
"formal": [
"Follow strict business letter format",
"Use traditional and respectful language",
"Emphasize credentials and qualifications",
],
}
return tips_by_tone.get(
tone, ["Tailor the letter to the specific job and company"]
)
def get_cover_letter_template(self, tone: str = "professional") -> Dict[str, Any]:
"""
Get a basic cover letter template for the specified tone.
Args:
tone: Desired tone for the template
Returns:
Dict with template structure and guidelines
"""
templates = {
"professional": {
"structure": [
"Header with your contact information",
"Date and employer contact information",
"Professional greeting",
"Opening paragraph: Position and brief introduction",
"Body paragraph 1: Relevant experience and skills",
"Body paragraph 2: Achievements and value proposition",
"Closing paragraph: Next steps and gratitude",
"Professional closing",
],
"sample_opening": "I am writing to express my strong interest in the [Position Title] role at [Company Name].",
"sample_closing": "I would welcome the opportunity to discuss how my experience can contribute to your team's success.",
},
"enthusiastic": {
"structure": [
"Energetic opening that shows excitement",
"Passionate description of relevant experience",
"Connection to company mission and values",
"Enthusiastic closing with call to action",
],
"sample_opening": "I am thrilled to apply for the [Position Title] position at [Company Name]!",
"sample_closing": "I can't wait to bring my passion and skills to your amazing team!",
},
}
return {
"success": True,
"tone": tone,
"template": templates.get(tone, templates["professional"]),
"tips": self._get_cover_letter_tips(tone),
}