File size: 17,323 Bytes
4fd18a2 4a5b92f 4fd18a2 4a5b92f 4fd18a2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 |
#!/usr/bin/env python3
"""
Job Search MCP Server - Smart job matching and instant application helper
Main entry point for the MCP server that exposes the four core endpoints:
1. profile.upsert - Store user rΓ©sumΓ©, skills, salary wish, and career goals
2. jobs.search - Pull fresh job posts, rank with GPU embeddings, return fit scores
3. letter.generate - Create personalized cover letters using LLM
4. qa.reply - Draft concise answers to client questions
This server uses both GPU processing for embeddings and LLM APIs for text generation,
demonstrating efficient use of both credit pools.
"""
import asyncio
import gradio as gr
from typing import Dict, Any
from src.tools import ProfileTool, JobSearchTool, CoverLetterTool, QATool
from src.config import get_settings
class JobSearchMCPServer:
"""Main MCP server class integrating all job search tools."""
def __init__(self):
self.settings = get_settings()
# Initialize all tools
self.profile_tool = ProfileTool()
self.job_search_tool = JobSearchTool()
self.cover_letter_tool = CoverLetterTool()
self.qa_tool = QATool()
print(f"π Job Search MCP Server initialized")
print(f"π GPU Embeddings: {self.settings.embedding_model}")
print(f"π€ LLM Provider: {self.settings.llm_provider}")
# Core MCP Endpoints
def profile_upsert(self, user_id: str, profile_data: str) -> Dict[str, Any]:
"""
MCP Endpoint: profile.upsert
Stores the user rΓ©sumΓ©, skills, salary expectations, and career goals.
Keeps personal context so every later call is tailored.
"""
return self.profile_tool.upsert(user_id, profile_data)
def jobs_search(
self, user_id: str, query: str = "", location: str = "", job_type: str = ""
) -> Dict[str, Any]:
"""
MCP Endpoint: jobs.search
Pulls fresh job posts, ranks them with GPU embeddings, and returns a fit score.
Users see the most relevant roles first, no endless scrolling.
"""
return self.job_search_tool.search(user_id, query, location, job_type)
def letter_generate(
self, user_id: str, job_description: str, tone: str = "professional"
) -> Dict[str, Any]:
"""
MCP Endpoint: letter.generate
Calls an LLM to create a short, personalized cover letter in any tone.
Saves time and improves response quality.
"""
return self.cover_letter_tool.generate(user_id, job_description, tone)
def qa_reply(
self, user_id: str, question: str, context: str = ""
) -> Dict[str, Any]:
"""
MCP Endpoint: qa.reply
Drafts concise answers to client questions like "Why should we hire you?"
Speeds up Upwork, Fiverr, or LinkedIn chats.
"""
return self.qa_tool.reply(user_id, question, context)
# Additional Helper Endpoints
def get_server_stats(self) -> Dict[str, Any]:
"""Get server statistics and health information."""
try:
from src.services import EmbeddingService
embedding_service = EmbeddingService()
embed_stats = embedding_service.get_index_stats()
return {
"success": True,
"server_info": {
"app_name": self.settings.app_name,
"embedding_model": self.settings.embedding_model,
"llm_provider": self.settings.llm_provider,
"llm_model": self.settings.llm_model,
},
"embedding_stats": embed_stats,
"endpoints": [
"profile.upsert",
"jobs.search",
"letter.generate",
"qa.reply",
],
}
except Exception as e:
return {
"success": False,
"message": f"Error getting server stats: {str(e)}",
}
# Initialize the MCP server
mcp_server = JobSearchMCPServer()
# Create Gradio interface for easy testing and demonstration
def create_gradio_interface():
"""Create a Gradio interface for the MCP server."""
with gr.Blocks(
title="Job Search MCP Server",
theme=gr.themes.Soft(),
css="""
.gradio-container {
max-width: 1200px !important;
}
.main-header {
text-align: center;
background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
}
""",
) as demo:
# Header
gr.HTML("""
<div class="main-header">
<h1>π Job Search MCP Server</h1>
<p>Smart job matching and instant application helper</p>
<p><strong>4 Core Endpoints:</strong> profile.upsert | jobs.search | letter.generate | qa.reply</p>
</div>
""")
# Server Stats
with gr.Row():
with gr.Column():
stats_button = gr.Button("π Get Server Stats", variant="secondary")
stats_output = gr.JSON(label="Server Statistics")
stats_button.click(fn=mcp_server.get_server_stats, outputs=stats_output)
# Main endpoints in tabs
with gr.Tabs():
# Profile Management Tab
with gr.Tab("π€ Profile Management (profile.upsert)"):
gr.Markdown("### Store and update your professional profile")
gr.Markdown(
"*This endpoint keeps personal context so every later call is tailored*"
)
with gr.Row():
with gr.Column():
profile_user_id = gr.Textbox(
label="User ID",
placeholder="Enter your unique user ID (e.g., john_doe_2024)",
value="demo_user",
)
profile_data = gr.TextArea(
label="Profile Data (JSON)",
placeholder='{\n "resume": "Full resume text here...",\n "skills": ["Python", "JavaScript", "React", "Node.js"],\n "salary_wish": "$80,000 - $120,000 annually",\n "career_goals": "Looking to transition into a senior full-stack developer role at a tech company",\n "experience_level": "Mid-level",\n "location": "Remote",\n "education": "BS Computer Science"\n}',
lines=8,
)
profile_submit = gr.Button(
"πΎ Update Profile", variant="primary"
)
with gr.Column():
profile_output = gr.JSON(label="Response")
# Quick profile actions
with gr.Row():
get_profile_btn = gr.Button(
"ποΈ View Profile", variant="secondary"
)
delete_profile_btn = gr.Button(
"ποΈ Delete Profile", variant="secondary"
)
profile_submit.click(
fn=mcp_server.profile_upsert,
inputs=[profile_user_id, profile_data],
outputs=profile_output,
)
get_profile_btn.click(
fn=mcp_server.profile_tool.get,
inputs=[profile_user_id],
outputs=profile_output,
)
delete_profile_btn.click(
fn=mcp_server.profile_tool.delete,
inputs=[profile_user_id],
outputs=profile_output,
)
# Job Search Tab
with gr.Tab("π Job Search (jobs.search)"):
gr.Markdown(
"### Find and rank relevant job opportunities with GPU embeddings"
)
gr.Markdown(
"*Pulls fresh job posts, ranks them with GPU embeddings, and returns fit scores*"
)
with gr.Row():
with gr.Column():
search_user_id = gr.Textbox(label="User ID", value="demo_user")
search_query = gr.Textbox(
label="Search Query",
placeholder="e.g., Python developer, Data scientist, Frontend engineer",
)
with gr.Row():
search_location = gr.Textbox(
label="Location",
placeholder="e.g., Remote, New York, San Francisco",
)
search_job_type = gr.Dropdown(
label="Job Type",
choices=[
"full-time",
"part-time",
"contract",
"freelance",
"remote",
],
value="full-time",
)
search_submit = gr.Button("π Search Jobs", variant="primary")
with gr.Column():
search_output = gr.JSON(label="Job Results with Fit Scores")
# Additional job search features
suggestions_btn = gr.Button(
"π‘ Get Search Suggestions", variant="secondary"
)
clear_cache_btn = gr.Button(
"ποΈ Clear Job Cache", variant="secondary"
)
search_submit.click(
fn=mcp_server.jobs_search,
inputs=[
search_user_id,
search_query,
search_location,
search_job_type,
],
outputs=search_output,
)
suggestions_btn.click(
fn=mcp_server.job_search_tool.get_search_suggestions,
inputs=[search_user_id],
outputs=search_output,
)
clear_cache_btn.click(
fn=mcp_server.job_search_tool.clear_job_cache, outputs=search_output
)
# Cover Letter Generator Tab
with gr.Tab("π Cover Letter (letter.generate)"):
gr.Markdown("### Generate personalized cover letters using LLM")
gr.Markdown(
"*Creates short, personalized cover letters in any tone - saves time and improves quality*"
)
with gr.Row():
with gr.Column():
letter_user_id = gr.Textbox(label="User ID", value="demo_user")
letter_tone = gr.Dropdown(
label="Tone",
choices=[
"professional",
"casual",
"enthusiastic",
"formal",
],
value="professional",
)
letter_job_desc = gr.TextArea(
label="Job Description",
placeholder="Paste the complete job description here...",
lines=6,
)
letter_submit = gr.Button(
"π Generate Cover Letter", variant="primary"
)
with gr.Column():
letter_output = gr.JSON(label="Generated Cover Letter")
# Additional cover letter features
multiple_tones_btn = gr.Button(
"π Generate Multiple Tones", variant="secondary"
)
template_btn = gr.Button("π Get Template", variant="secondary")
letter_submit.click(
fn=mcp_server.letter_generate,
inputs=[letter_user_id, letter_job_desc, letter_tone],
outputs=letter_output,
)
multiple_tones_btn.click(
fn=mcp_server.cover_letter_tool.generate_multiple_tones,
inputs=[letter_user_id, letter_job_desc],
outputs=letter_output,
)
template_btn.click(
fn=mcp_server.cover_letter_tool.get_cover_letter_template,
inputs=[letter_tone],
outputs=letter_output,
)
# Q&A Assistant Tab
with gr.Tab("π¬ Q&A Assistant (qa.reply)"):
gr.Markdown(
"### Get help with interview questions and client responses"
)
gr.Markdown(
"*Drafts concise answers to speed up Upwork, Fiverr, or LinkedIn chats*"
)
with gr.Row():
with gr.Column():
qa_user_id = gr.Textbox(label="User ID", value="demo_user")
qa_question = gr.TextArea(
label="Question",
placeholder="e.g., Why should we hire you?\nWhat's your experience with Python?\nHow much do you charge for this project?",
lines=4,
)
qa_context = gr.Textbox(
label="Context (optional)",
placeholder="Additional context about the role or conversation...",
)
qa_submit = gr.Button("π¬ Generate Response", variant="primary")
with gr.Column():
qa_output = gr.JSON(label="Generated Response")
# Additional Q&A features
with gr.Row():
common_questions_btn = gr.Button(
"β Common Questions", variant="secondary"
)
practice_session_btn = gr.Button(
"π― Practice Session", variant="secondary"
)
qa_submit.click(
fn=mcp_server.qa_reply,
inputs=[qa_user_id, qa_question, qa_context],
outputs=qa_output,
)
common_questions_btn.click(
fn=lambda: mcp_server.qa_tool.get_common_questions("developer"),
outputs=qa_output,
)
practice_session_btn.click(
fn=lambda uid: mcp_server.qa_tool.practice_session(
uid, "developer", 3
),
inputs=[qa_user_id],
outputs=qa_output,
)
# Footer with usage information
gr.HTML("""
<div style="margin-top: 40px; padding: 20px; background-color: #f0f0f0; border-radius: 10px;">
<h3>π― How It Works</h3>
<p><strong>GPU Part (T4-small):</strong> The server embeds user profile text and each job post with a modern sentence-embedding model. A FAISS index runs similarity search in real time.</p>
<p><strong>Inference-API Part:</strong> A hosted LLM writes cover letters and Q&A replies. Average call is under 300 tokens.</p>
<p><strong>Typical User Flow:</strong></p>
<ol>
<li>Upload rΓ©sumΓ© and skills once using <code>profile.upsert</code></li>
<li>Call <code>jobs.search</code> with a role keyword (e.g., "LLM engineer")</li>
<li>Get a ranked list of matches with fit percentages</li>
<li>Pick a job ID and call <code>letter.generate</code> to copy a ready cover letter</li>
<li>When the recruiter asks something, send the question to <code>qa.reply</code> for an instant answer</li>
</ol>
<p><strong>Benefits:</strong> Cuts application time by 80%+, reduces copy-pasted cover letters, improves job-to-skill matching</p>
</div>
""")
return demo
def main():
"""Main entry point for the application."""
print("π Starting Job Search MCP Server...")
# Create and launch Gradio interface
demo = create_gradio_interface()
# Launch with MCP enabled
demo.launch(
server_name=mcp_server.settings.host,
server_port=mcp_server.settings.port,
mcp_server=True,
share=False,
show_error=True
)
if __name__ == "__main__":
main()
|