get_proposals
Retrieve your submitted Upwork proposals with status, bid rate, submission date, and client interview status to identify proposals requiring follow-up.
Instructions
Get list of your submitted proposals on Upwork. Shows status, bid rate, submission date, and whether the client is interviewing you. Use this to track which proposals need follow-up.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| status | No | Filter by status. Default: "active" | |
| limit | No | Max results. Default: 20 |
Implementation Reference
- src/tools/get-proposals.ts:27-89 (handler)Main handler function that navigates to Upwork proposals page, scrapes proposal cards from the DOM (title, URL, status, bid rate, client info, interviewing status), and returns filtered results.
export async function getProposals(input: GetProposalsInput): Promise<ProposalItem[]> { const page = await ensureLoggedIn(); try { const url = input.status === 'archived' ? 'https://www.upwork.com/nx/proposals/archived' : 'https://www.upwork.com/nx/proposals'; console.error('[getProposals] Navigating to:', url); await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 }); await humanDelay(2000, 4000); await page.waitForSelector('[data-test="proposal-list"], .proposals-list, article', { timeout: 15000, }).catch(() => console.error('[getProposals] Selector not found, extracting anyway')); await humanDelay(1000, 2000); const proposals = await page.evaluate((limit: number): ProposalItem[] => { const cards = document.querySelectorAll( '[data-test="proposal-list-item"], .proposal-item, article' ); const results: ProposalItem[] = []; cards.forEach((card, i) => { if (i >= limit) return; const titleEl = card.querySelector('a[href*="/jobs/"], a[href*="~"]'); const title = titleEl?.textContent?.trim() ?? ''; const href = titleEl?.getAttribute('href') ?? ''; const job_url = href.startsWith('http') ? href : `https://www.upwork.com${href}`; const idMatch = href.match(/~([a-z0-9]+)/); const id = idMatch?.[1] ?? `proposal_${i}`; results.push({ id, job_title: title, job_url, status: card.querySelector('[data-test="status"], .status-badge')?.textContent?.trim() ?? '', bid_rate: card.querySelector('[data-test="bid-rate"], .bid-rate')?.textContent?.trim() ?? '', submitted_at: card.querySelector('time, [data-test="submitted-at"]')?.textContent?.trim() ?? '', client_name: card.querySelector('[data-test="client-name"]')?.textContent?.trim() ?? '', client_location: card.querySelector('[data-test="client-location"]')?.textContent?.trim() ?? '', interviewing: card.textContent?.toLowerCase().includes('interviewing') ?? false, }); }); return results; }, input.limit); console.error(`[getProposals] Found ${proposals.length} proposals`); return proposals.filter((p) => p.job_title); } finally { await page.close(); } } - src/tools/get-proposals.ts:4-11 (schema)Zod schema defining input parameters: status (active/archived/all, default 'active') and limit (default 20).
export const GetProposalsSchema = z.object({ status: z .enum(['active', 'archived', 'all']) .optional() .default('active') .describe('Filter proposals by status'), limit: z.coerce.number().optional().default(20).describe('Max proposals to return'), }); - src/tools/get-proposals.ts:15-25 (schema)TypeScript interface for ProposalItem output type with id, job_title, job_url, status, bid_rate, submitted_at, client_name, client_location, interviewing fields.
export interface ProposalItem { id: string; job_title: string; job_url: string; status: string; bid_rate: string; submitted_at: string; client_name: string; client_location: string; interviewing: boolean; } - src/index.ts:131-146 (registration)MCP tool registration in main index.ts with name 'get_proposals', description, and inputSchema (JSON Schema format for MCP protocol).
{ name: 'get_proposals', description: `Get list of your submitted proposals on Upwork. Shows status, bid rate, submission date, and whether the client is interviewing you. Use this to track which proposals need follow-up.`, inputSchema: { type: 'object', properties: { status: { type: 'string', enum: ['active', 'archived', 'all'], description: 'Filter by status. Default: "active"', }, limit: { type: ['number', 'string'], description: 'Max results. Default: 20' }, }, }, - src/worker.ts:11-11 (registration)Worker import and dispatch routing: case 'get_proposals' calls getProposals with GetProposalsSchema.parse(args).
import { getProposals, GetProposalsSchema } from './tools/get-proposals.js';