upwork-mcp
by kubegrind
README.md
# 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](https://docs.astral.sh/uv/) (recommended) or pip
- A valid Upwork account
## Installation
### Using uv (recommended)
```bash
git clone https://github.com/kubegrind/upwork-mcp
cd upwork-mcp
uv sync
uv run playwright install chromium
```
### Using pip
```bash
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):
```json
{
"mcpServers": {
"upwork": {
"command": "uv",
"args": [
"--directory",
"/absolute/path/to/upwork-mcp",
"run",
"upwork-mcp"
]
}
}
}
```
### Claude Code
```bash
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:
```bash
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:
```json
{
"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`) require `confirmed: 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
```bash
uv sync --extra dev
uv run pytest
uv run ruff check src
uv run mypy src
```
## License
MIT