π¨ 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), | |
} | |