upwork-mcp
An MCP server for Upwork automation. Search jobs, draft and submit proposals, manage messages and contracts — all from Claude Desktop or any MCP-compatible client.
Features
- Job Search — keyword search with filters (budget, hourly rate, experience level, client rating, recency), automatic scoring and red-flag detection
- Proposal Workflow — draft from templates, preview, then submit with explicit confirmation; daily limit enforced
- Client Vetting — 7-condition red-flag checker (payment verification, rating, hire history, account age, repost count, proposal volume, hourly rate floor)
- Messages — list unread conversations, read threads, draft and send replies
- Contracts — list active/past contracts, view details and work diary entries
- Session Safety — persistent Chromium profile, Cloudflare detection, 7-day idle expiry, domain restriction to
upwork.com - Audit Log — every tool call appended to
~/.upwork-mcp/audit.log(metadata only, no content)
Requirements
- Python 3.11+
- uv (recommended) or pip
- A valid Upwork account
Installation
Using uv (recommended)
git clone https://github.com/kubegrind/upwork-mcp
cd upwork-mcp
uv sync
uv run playwright install chromium
Using pip
git clone https://github.com/kubegrind/upwork-mcp
cd upwork-mcp
pip install -e .
playwright install chromium
Configuration
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"upwork": {
"command": "uv",
"args": [
"--directory",
"/absolute/path/to/upwork-mcp",
"run",
"upwork-mcp"
]
}
}
}
Claude Code
claude mcp add upwork -- uv --directory /absolute/path/to/upwork-mcp run upwork-mcp
Environment Variables
| Variable | Default | Description |
|---|---|---|
UPWORK_MCP_HOME | ~/.upwork-mcp | Data directory for profile, logs, templates |
UPWORK_MCP_MIN_HOURLY_RATE | 25 | Minimum acceptable hourly rate for red-flag detection |
Authentication
The server uses a persistent Chromium browser profile. On first run, authenticate manually:
uv run upwork-mcp --no-headless
A browser window will open. Log in to Upwork normally. The session is saved to ~/.upwork-mcp/profile/ and reused on subsequent runs. Sessions expire after 7 days of inactivity.
Tools
Jobs
| Tool | Description |
|---|---|
upwork_search_jobs | Search by keyword with filters; returns scored, ranked results |
upwork_get_job_details | Full job details including client history and interview rate |
upwork_score_job | Score a specific job 1–10 |
upwork_flag_client | Check a client against 7 red-flag conditions |
upwork_run_saved_search | Run a saved search from ~/.upwork-mcp/searches/<name>.json |
Proposals
| Tool | Description |
|---|---|
upwork_get_proposals | List submitted proposals with status |
upwork_get_proposal_details | Details for a specific proposal |
upwork_draft_proposal | Draft a proposal from a template |
upwork_preview_proposal | Preview a draft before submission |
upwork_submit_proposal | WRITE — submit a proposal (requires confirmed: true) |
upwork_withdraw_proposal | WRITE — withdraw a submitted proposal (requires confirmed: true) |
Messages
| Tool | Description |
|---|---|
upwork_get_messages | List unread conversations |
upwork_get_conversation | Read the last 10 messages in a thread |
upwork_draft_message | Draft a reply for review |
upwork_send_message | WRITE — send a message (requires confirmed: true) |
Contracts
| Tool | Description |
|---|---|
upwork_get_contracts | List active and past contracts |
upwork_get_contract_details | Contract details and milestones |
upwork_get_work_diary | Hours and memo entries for a contract |
Profile
| Tool | Description |
|---|---|
upwork_get_my_profile | Name and title from your profile |
upwork_get_connects_balance | Current connects balance with low-balance warning |
upwork_get_profile_stats | JSS, Top Rated status, and total earnings |
Session & Audit
| Tool | Description |
|---|---|
upwork_check_session | Verify the session is authenticated |
upwork_close_session | WRITE — close the browser session |
upwork_rotate_session | WRITE — restart the browser context |
upwork_view_audit_log | View the last N audit log entries |
Proposal Templates
Templates live in ~/.upwork-mcp/templates/. The default template is loaded from default.txt if it exists. Pass a filename via template_name to use a custom template.
Supported variables:
{job_title} {client_name} {key_skill}
{estimated_timeline} {rate} {years_experience}
{industries} {custom_paragraph} {start_date}
{your_name}
Example template (~/.upwork-mcp/templates/default.txt):
Hi {client_name},
I noticed you're looking for {key_skill} help on {job_title}.
I have {years_experience} years of relevant experience and can deliver {estimated_timeline}.
My rate is {rate}.
{your_name}
Saved Searches
Create JSON files in ~/.upwork-mcp/searches/ to save search configurations:
{
"query": "devops kubernetes",
"experience_level": "expert",
"budget_min": 500,
"client_rating_min": 4.5
}
Run with upwork_run_saved_search(search_name="devops").
Job Scoring
Jobs are scored 1–10 using a weighted model:
| Factor | Weight |
|---|---|
| Budget match | 30% |
| Skill match | 25% |
| Client quality | 20% |
| Proposal competition | 15% |
| Recency | 10% |
Jobs scoring below 4.0 are filtered from results. Jobs scoring 7.0 or above are marked [HIGH PRIORITY].
Safety
- WRITE tools (
submit_proposal,withdraw_proposal,send_message,close_session,rotate_session) requireconfirmed: true— the model cannot act without explicit user confirmation - Daily proposal limit — hard block at 10 submissions per day
- Domain restriction — browser navigation is restricted to
upwork.com - No credential exposure — cookies and tokens are never returned, logged, or printed
- Path traversal protection — template and saved search filenames are validated with
Path.is_relative_to() - Audit log — all tool calls recorded with timestamp, action type, target, and status
Development
uv sync --extra dev
uv run pytest
uv run ruff check src
uv run mypy src
License
MIT