cv-mcp / app.py
Nirav Madhani
feat: Add CV Markdown to PDF converter with MCP integration
9f2d44c
import gradio as gr
import requests
import os
from dotenv import load_dotenv
import tempfile
# Load environment variables
load_dotenv()
def get_sample_cv():
"""
Return the sample CV markdown content for reference
"""
try:
with open('Sample.md', 'r', encoding='utf-8') as f:
return f.read()
except FileNotFoundError:
return "Sample.md file not found in the current directory."
except Exception as e:
return f"Error reading Sample.md: {str(e)}"
def get_cv_formatting_instructions():
"""
Return detailed CV formatting instructions from external markdown file
"""
try:
with open('Instructions.md', 'r', encoding='utf-8') as f:
return f.read()
except FileNotFoundError:
return "Instructions.md file not found in the current directory."
except Exception as e:
return f"Error reading Instructions.md: {str(e)}"
def convert_markdown_to_pdf(markdown_text):
"""
Convert markdown text to PDF and HTML using the external API endpoints
Args:
markdown_text (str): The markdown text to convert
Returns:
tuple: (pdf_path, html_content, status_message)
"""
if not markdown_text.strip():
return None, "", "Please enter some markdown text."
# Get bearer token from environment
bearer_token = os.getenv('BEARER_TOKEN')
if not bearer_token or bearer_token == 'your_bearer_token_here':
return None, "", "❌ Bearer token not configured. Please set BEARER_TOKEN in .env file."
try:
headers = {
'Authorization': f'Bearer {bearer_token}',
'Content-Type': 'text/markdown'
}
# Get base URL from environment
base_url = os.getenv('API_BASE_URL', 'https://nirav-madhani-resume-server.hf.space')
# Get PDF directly from API
pdf_url = f"{base_url}/convert/pdf"
print(f"📡 Making request to {pdf_url}")
pdf_response = requests.post(pdf_url, data=markdown_text, headers=headers, timeout=30)
if pdf_response.status_code != 200:
return None, "", f"❌ PDF API request failed with status {pdf_response.status_code}: {pdf_response.text}"
# Save PDF to temporary file
with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf', mode='w+b') as temp_pdf:
temp_pdf.write(pdf_response.content)
pdf_path = temp_pdf.name
# Get HTML for preview
html_url = f"{base_url}/convert/html"
print(f"📡 Making request to {html_url}")
html_response = requests.post(html_url, data=markdown_text, headers=headers, timeout=30)
if html_response.status_code != 200:
# PDF worked but HTML failed - still return PDF
return pdf_path, "", f"✅ PDF generated successfully! (HTML preview failed: {html_response.status_code})"
html_content = html_response.text
return pdf_path, html_content, "✅ PDF and HTML generated successfully via API!"
except requests.exceptions.RequestException as e:
return None, "", f"❌ Network error: {str(e)}"
except Exception as e:
return None, "", f"❌ Error generating PDF: {str(e)}"
def process_conversion(markdown_text):
"""
Process the markdown conversion and return results for Gradio interface
Args:
markdown_text (str): The markdown text to convert
Returns:
tuple: (pdf_file_path, html_content, status_message)
"""
pdf_path, html_content, message = convert_markdown_to_pdf(markdown_text)
return pdf_path, html_content, message
# MCP Tool Functions (callable by AI assistants)
def mcp_get_sample_cv():
"""MCP tool: Get sample CV markdown
Returns:
str: The sample CV markdown content
"""
return get_sample_cv()
def mcp_get_cv_instructions():
"""MCP tool: Get CV formatting instructions
Returns:
str: The CV formatting instructions
"""
return get_cv_formatting_instructions()
def mcp_convert_cv(markdown_text: str):
"""MCP tool: Convert CV markdown to PDF
Args:
markdown_text (str): The markdown text to convert to PDF
Returns:
str: The conversion result message
"""
pdf_path, html_content, message = process_conversion(markdown_text)
return f"Conversion result: {message}"
# Create Gradio interface
with gr.Blocks(title="CV Markdown to PDF Converter", theme=gr.themes.Soft()) as demo:
gr.Markdown("# 📄 CV Markdown to PDF Converter")
gr.Markdown("Convert your CV markdown to professional PDF format. Use the Instructions & Sample tab to learn the proper formatting.")
with gr.Tabs():
with gr.TabItem("🚀 Convert CV"):
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("## Input")
markdown_input = gr.Textbox(
label="CV Markdown Text",
placeholder="Enter your CV markdown here... (see Instructions & Sample tab for formatting guide)",
lines=20,
max_lines=30
)
with gr.Column(scale=1):
gr.Markdown("## Output")
with gr.Tabs():
with gr.TabItem("📄 PDF Download"):
pdf_output = gr.File(
label="Generated PDF",
file_types=[".pdf"]
)
with gr.TabItem("🌐 HTML Preview"):
html_output = gr.HTML(
label="HTML Preview",
value="HTML preview will appear here after conversion..."
)
status_message = gr.Textbox(
label="Status",
interactive=False,
lines=2
)
with gr.Row():
convert_btn = gr.Button("🚀 Convert to PDF", variant="primary", size="lg")
# Event handler
convert_btn.click(
fn=process_conversion,
inputs=[markdown_input],
outputs=[pdf_output, html_output, status_message]
)
with gr.TabItem("📋 Instructions & Sample"):
with gr.Tabs():
with gr.TabItem("📖 Formatting Instructions"):
gr.Markdown(get_cv_formatting_instructions())
with gr.TabItem("📄 Sample CV"):
gr.Markdown("## Sample CV Markdown")
gr.Markdown("Below is a complete sample CV showing all the formatting patterns:")
sample_cv_display = gr.Code(
value=get_sample_cv(),
language="markdown",
label="Sample CV Markdown Code",
lines=30,
max_lines=50
)
copy_sample_btn = gr.Button("📋 Copy Sample to Converter", variant="secondary")
# Copy sample to main input
copy_sample_btn.click(
fn=get_sample_cv,
outputs=[markdown_input]
)
# Example CV templates
with gr.Row():
gr.Examples(
examples=[
[get_sample_cv()],
["""---
name: John Doe
header:
- text: |
<span style="font-size: 1.2em; font-weight: bold;">Full Stack Developer</span>
- text: <span class="iconify" data-icon="tabler:phone"></span> (555) 987-6543
newLine: true
- text: <span class="iconify" data-icon="tabler:mail"></span> john.doe@email.com
link: mailto:john.doe@email.com
---
## Experience
[L2]Tech Startup Inc.[/L2] [L2R]San Francisco, CA[/L2R]
[L3]Full Stack Developer[/L3] [L3R]Jan 2022 - Present[/L3R]
- Designed and implemented scalable web applications serving 10K+ users
- Reduced page load times by 40% through optimization techniques
- Led migration from monolith to microservices architecture
## Education
[L2]University of California[/L2] GPA 3.8 [L2R]Berkeley, CA[/L2R]
[L3]B.S. in Computer Science[/L3] [L3R]2018 - 2022[/L3R]
## Skills
**Programming:** Python, JavaScript, TypeScript, Java
<br>
**Frameworks:** React, Node.js, Django, Spring Boot
<br>
**Databases:** PostgreSQL, MongoDB, Redis
"""],
],
inputs=[markdown_input],
label="📝 CV Templates"
)
# Register MCP tools
gr.api(mcp_get_sample_cv)
gr.api(mcp_get_cv_instructions)
gr.api(mcp_convert_cv)
# Launch the app with MCP enabled
if __name__ == "__main__":
demo.launch(
mcp_server=True
)