analyze_job
Score job opportunities across 5 dimensions to decide whether to apply. Evaluates niche fit, client quality, budget fit, competition, and project clarity. Get a grade, recommendation, and suggested bid to inform proposal decisions.
Instructions
Analyze a job opportunity and score it across 5 dimensions to decide whether to apply.
Scoring breakdown (100 pts max):
Niche Fit (30pts): How well the job matches n8n/automation keywords
Client Quality (25pts): Rating, total spent, hire rate, location
Budget Fit (20pts): Budget vs your target rate, estimated total project value
Competition (10pts): Number of existing proposals (fewer = better)
Project Clarity (10pts): How well-defined the scope is
Red Flag Penalty (-5pts each): Vague scope, low budget signals, no client history
Returns: grade (A+/A/B/C/D/F), recommendation (APPLY NOW/APPLY/CONSIDER/SKIP/AVOID), suggested bid, estimated project value, key selling points, and proposal tips.
RECOMMENDED WORKFLOW:
search_jobs → find candidates
get_job_details → get full info
analyze_job → score and decide
submit_proposal → if grade A or B
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| job | Yes | Full job data object from get_job_details | |
| my_rate | No | Your target hourly rate in USD (defaults to BID_RATE_DEFAULT env var) |
Implementation Reference
- src/tools/analyze-job.ts:333-431 (handler)Main handler function analyzeJob() that orchestrates all scoring dimensions (niche fit, client quality, budget fit, competition, project clarity, red flags), computes a total score, assigns a grade (A+ through F), and returns a JobAnalysis with recommendation, suggested bid, selling points, and proposal tips.
export async function analyzeJob(input: AnalyzeJobInput): Promise<JobAnalysis> { const myRate = input.my_rate ?? config.bid.default; const job = input.job; // Run all scorers const nicheFit = scoreNicheFit(job); const clientQuality = scoreClientQuality(job.client); const budgetFit = scoreBudgetFit(job, myRate); const competition = scoreCompetition(job.proposals_count ?? ''); const projectClarity = scoreProjectClarity(job); const redFlags = detectRedFlags(job); const rawScore = nicheFit.score + clientQuality.score + budgetFit.score + competition.score + projectClarity.score; const penalty = redFlags.count * Math.abs(WEIGHTS.red_flags); const totalScore = Math.max(0, rawScore - penalty); const maxScore = WEIGHTS.niche_fit + WEIGHTS.client_quality + WEIGHTS.budget_fit + WEIGHTS.competition + WEIGHTS.project_clarity; const pct = (totalScore / maxScore) * 100; // Grade let grade: JobAnalysis['grade']; if (pct >= 90) grade = 'A+'; else if (pct >= 75) grade = 'A'; else if (pct >= 60) grade = 'B'; else if (pct >= 45) grade = 'C'; else if (pct >= 30) grade = 'D'; else grade = 'F'; // Recommendation let recommendation: JobAnalysis['recommendation']; if (pct >= 80 && redFlags.count === 0) recommendation = 'APPLY NOW'; else if (pct >= 65) recommendation = 'APPLY'; else if (pct >= 45) recommendation = 'CONSIDER'; else if (pct >= 30) recommendation = 'SKIP'; else recommendation = 'AVOID'; // Key selling points for this specific job const keySellingPoints: string[] = []; const text = job.description.toLowerCase(); if (text.includes('n8n')) keySellingPoints.push('Direct n8n experience — mention specific nodes/workflows'); if (text.includes('webhook')) keySellingPoints.push('Webhook expertise — show real-time integration examples'); if (text.includes('api')) keySellingPoints.push('API integration — reference similar REST/GraphQL projects'); if (text.includes('crm') || text.includes('hubspot') || text.includes('salesforce')) keySellingPoints.push('CRM automation — mention lead/deal workflow experience'); if (text.includes('slack') || text.includes('discord')) keySellingPoints.push('Messaging platform automation — notification workflows'); if (text.includes('google sheet') || text.includes('airtable')) keySellingPoints.push('Spreadsheet automation — data sync/reporting workflows'); if (keySellingPoints.length === 0) keySellingPoints.push('Highlight n8n as the right tool for their automation needs'); // Proposal tips const proposalTips: string[] = [ "Open with THEIR problem, not your bio", `Mention ${(job.skills ?? []).slice(0, 3).join(', ') || 'their specific tools'} directly`, ]; if ((job.screening_questions ?? []).length > 0) { proposalTips.push(`Answer all ${job.screening_questions!.length} screening questions thoughtfully`); } if (pct >= 65) proposalTips.push('Include a brief workflow diagram or approach outline'); if (competition.score < 5) proposalTips.push('High competition — make your opener unique and specific'); return { job_title: job.title, job_url: job.url ?? '', total_score: totalScore, max_score: maxScore, grade, recommendation, breakdown: { niche_fit: nicheFit, client_quality: clientQuality, budget_fit: { score: budgetFit.score, max: budgetFit.max, details: budgetFit.details, estimated_value: budgetFit.estimated_value, }, competition, project_clarity: projectClarity, red_flags: { count: redFlags.count, penalty, flags: redFlags.flags, }, }, suggested_bid: budgetFit.recommendation, estimated_project_value: budgetFit.estimated_value, key_selling_points: keySellingPoints, proposal_tips: proposalTips, risk_factors: redFlags.flags, }; } - src/tools/analyze-job.ts:5-42 (schema)Zod schema (AnalyzeJobSchema) and type (AnalyzeJobInput) defining the input shape: a job object (title, description, budget, client, etc.) and an optional my_rate. Also exports the JobAnalysis output interface (lines 308-331).
export const AnalyzeJobSchema = z.object({ job: z .object({ id: z.string().optional(), title: z.string(), url: z.string().optional(), description: z.string(), budget: z.string().optional(), job_type: z.string().optional(), duration: z.string().optional(), experience_level: z.string().optional(), posted_at: z.string().optional(), skills: z.array(z.string()).optional(), category: z.string().optional(), screening_questions: z.array(z.string()).optional(), proposals_count: z.string().optional(), connects_required: z.string().optional(), client: z .object({ name: z.string().optional(), location: z.string().optional(), rating: z.string().optional(), reviews_count: z.string().optional(), jobs_posted: z.string().optional(), hire_rate: z.string().optional(), total_spent: z.string().optional(), member_since: z.string().optional(), }) .optional(), }) .describe('Job data from get_job_details'), my_rate: z .coerce.number() .optional() .describe('Your target hourly rate in USD. Defaults to BID_RATE_DEFAULT from config.'), }); export type AnalyzeJobInput = z.infer<typeof AnalyzeJobSchema>; - src/index.ts:201-260 (registration)MCP tool registration in the direct (non-gateway) server: tool name 'analyze_job', detailed description, and JSON Schema input definition embedded in the TOOLS array.
{ name: 'analyze_job', description: `Analyze a job opportunity and score it across 5 dimensions to decide whether to apply. Scoring breakdown (100 pts max): - Niche Fit (30pts): How well the job matches n8n/automation keywords - Client Quality (25pts): Rating, total spent, hire rate, location - Budget Fit (20pts): Budget vs your target rate, estimated total project value - Competition (10pts): Number of existing proposals (fewer = better) - Project Clarity (10pts): How well-defined the scope is - Red Flag Penalty (-5pts each): Vague scope, low budget signals, no client history Returns: grade (A+/A/B/C/D/F), recommendation (APPLY NOW/APPLY/CONSIDER/SKIP/AVOID), suggested bid, estimated project value, key selling points, and proposal tips. RECOMMENDED WORKFLOW: 1. search_jobs → find candidates 2. get_job_details → get full info 3. analyze_job → score and decide 4. submit_proposal → if grade A or B`, inputSchema: { type: 'object', properties: { job: { type: 'object', description: 'Full job data object from get_job_details', properties: { title: { type: 'string' }, description: { type: 'string' }, url: { type: 'string' }, budget: { type: 'string' }, job_type: { type: 'string' }, duration: { type: 'string' }, experience_level: { type: 'string' }, skills: { type: 'array', items: { type: 'string' } }, proposals_count: { type: 'string' }, screening_questions: { type: 'array', items: { type: 'string' } }, client: { type: 'object', properties: { rating: { type: 'string' }, total_spent: { type: 'string' }, hire_rate: { type: 'string' }, reviews_count: { type: 'string' }, jobs_posted: { type: 'string' }, location: { type: 'string' }, member_since: { type: 'string' }, }, }, }, required: ['title', 'description'], }, my_rate: { type: 'number', description: 'Your target hourly rate in USD (defaults to BID_RATE_DEFAULT env var)', }, }, required: ['job'], }, }, - src/index.ts:328-332 (registration)CallToolRequestSchema handler switch case: validates args with AnalyzeJobSchema.parse() and calls analyzeJob() in the direct server mode.
case 'analyze_job': { const input = AnalyzeJobSchema.parse(args); result = await analyzeJob(input); break; } - src/gateway.ts:147-161 (registration)Gateway-mode registration: tool name 'analyze_job' with inputSchema and description. The actual execution is proxied to the worker (line 177 via callWorker).
{ name: 'analyze_job', description: `Score a job opportunity 0-100 across 5 dimensions: niche fit, client quality, budget fit, competition, clarity. Returns grade (A+/A/B/C/D/F), recommendation (APPLY NOW / APPLY / CONSIDER / SKIP / AVOID), suggested bid, selling points.`, inputSchema: { type: 'object', properties: { job: { description: 'Job object with title, description, budget, skills, client info', }, my_rate: { type: ['number', 'string'] }, }, required: ['job'], }, },