# Vibeworker MCP server

> Your ranked Upwork job feed, proposals, persona, and filters as 24 tools an AI assistant can call. Everything a signed-in user can do in the app, except billing and account deletion.

Server URL: https://tryvibeworker.com/api/mcp (streamable HTTP)
Human docs: https://tryvibeworker.com/upwork-mcp/docs
Setup: https://tryvibeworker.com/upwork-mcp

## Authentication

- Claude, ChatGPT, and other OAuth-capable clients: add the URL as a custom connector, sign in to Vibeworker, allow. OAuth 2.1 with dynamic client registration; no keys to paste.
- Claude Code, Cursor, Windsurf, scripts: `Authorization: Bearer vw_...` with an API key from https://tryvibeworker.com/settings (Developer tab).
- No account yet: https://tryvibeworker.com/signup (free, no card). The feed has data once onboarding is complete.

## Limits

- Subscribers: unlimited.
- Free accounts: a one-time pool of 25 fresh jobs across list_jobs, search_jobs, get_job, and get_proposal_context, then up to 100 jobs/day that are older than 24 hours. generate_proposal is subscribers only; free accounts use get_proposal_context and draft themselves.
- import_upwork_profile: 8/day. Every other tool is uncounted on every plan.
- Job IDs are the `vw_...` public IDs returned by list_jobs and search_jobs.

## Tools

### Jobs

#### list_jobs

Get your ranked Upwork job feed from Vibeworker. Returns jobs scored and ranked by match to your profile. Use this to find the best opportunities to apply to. (read-only)

Parameters:

- `limit` (number): Number of jobs to return (max 50)
- `sort` ("match" | "newest" | "budget" | "quick_win"): Sort order: match (best fit), newest, budget (highest pay), quick_win (fast completable)
- `jobType` ("Fixed" | "Hourly"): Filter to Fixed or Hourly jobs only
- `minBudget` (number): Minimum budget in USD
- `minScore` (number): Minimum match score 0-1 (e.g. 0.7 = top 30%)
- `categories` (string[]): Filter to specific Upwork categories

#### search_jobs

Search your Upwork job database by keyword. Searches job titles and descriptions from the last 48 hours. (read-only)

Parameters:

- `query` (string, required): Search query — keywords, skills, or job type
- `limit` (number): Number of results to return

#### get_job

Get full details for a specific Upwork job including description, client info, all scores, and direct Upwork URL. Use before drafting a proposal. (read-only)

Parameters:

- `id` (string, required): Job ID from list_jobs

#### update_job_status

Record what happened with a job. Stages (saved, applied, interviewing, won, lost) are stamped with the current time on the job's Activity row; pass undo:true to clear one. skipped hides the job from the feed. rejected logs that you passed on it. Notes are appended to the Activity row. Every stage that changes the dashboard card changes it here too. (writes)

Parameters:

- `jobId` (string, required): Job ID from list_jobs, search_jobs, get_job, or list_activity
- `action` ("saved" | "applied" | "interviewing" | "won" | "lost" | "skipped" | "rejected", required)
- `undo` (boolean): Clear this stage instead of setting it (e.g. applied by mistake)
- `notes` (string): Anything worth remembering about this job: who you spoke to, what they asked, when to follow up. Appended to the row's notes.

#### list_activity

The freelancer's Activity tab: every job they saved, drafted a proposal for, applied to, or recorded an outcome on, newest activity first, with the date each stage was reached and their notes. Rows outlive the job feed, so this is the place to answer "what happened with the jobs I applied to". Use update_job_status to move a job along; get_proposal to read a draft. (read-only)

Parameters:

- `days` (integer): Only rows with activity in the last N days (default 30). Omit and pass all:true for everything.
- `all` (boolean): Return all rows regardless of age
- `view` ("all" | "saved" | "proposals" | "applied" | "outcomes"): Filter to one stage group (default all)

### Proposals

#### get_proposal_context

Get everything needed to draft a proposal for a specific job: full job details, the client's screening questions, the freelancer's persona (projects, past jobs with client quotes, rate), their style notes, and the writing rules to follow. Call this before drafting a proposal yourself; subscribers can call generate_proposal instead. (read-only)

Parameters:

- `jobId` (string, required): Job ID from list_jobs or get_job

#### generate_proposal

Generate a proposal with Vibeworker's own writer: a cover letter, one answer per screening question, and which persona items it drew on. Uses the persona and style notes. Saved to the job so it appears in the dashboard. Subscribers only; free accounts should call get_proposal_context and draft it themselves. (writes)

Parameters:

- `jobId` (string, required): Job ID from list_jobs, search_jobs, or get_job

#### save_proposal

Save a proposal you wrote or edited for a job, so it sits on the job's Activity row next to the freelancer's notes and stages. Call get_proposal_context first for the job, screening questions, persona and writing rules. Calling again for the same job replaces the draft, so this is also how you revise one (including one from generate_proposal). Free on every plan. Does not submit anything to Upwork. (writes)

Parameters:

- `jobId` (string, required): Job ID from list_jobs, search_jobs, get_job, or list_activity
- `coverLetter` (string, required): The cover letter, plain text
- `answers` (object[]): One entry per screening question, in the order the job lists them

#### list_proposals

List proposals generated for this account, newest first, with the job each belongs to. (read-only)

Parameters:

- `limit` (integer): Default 10

### Persona

#### get_persona

Get the freelancer persona used to write proposals: name, title, rate, overview, skills, proof points, portfolio projects, past jobs with client quotes, employment, and the style notes proposals must follow. Separate from get_profile, which is the text the job feed is ranked against. (read-only)

No parameters.

#### update_persona

Update the freelancer persona used for proposals. Pass only the fields to change. Scalar fields replace, list fields (skills, projects, workHistory, employment) replace the whole list when given, so read with get_persona first and send the full list back. proposalNotes are free-text style instructions every proposal follows. (writes)

Parameters:

- `name` (string)
- `title` (string): Headline, e.g. "Full-stack developer, automation and AI integrations"
- `location` (string)
- `hourlyRate` (string): Exactly as it should appear in a proposal, e.g. "$45/hr"
- `experienceYears` (string): e.g. "8 years"
- `overview` (string): The Upwork bio text
- `skills` (string[])
- `proofPoints` (string): Job Success Score, badges, verifications, freeform
- `projects` (object[])
- `workHistory` (object[])
- `employment` (object[])
- `proposalNotes` (string): Style instructions for every proposal, e.g. "Always mention I work US hours. Never offer a discount."

#### import_upwork_profile

Parse the text of an Upwork profile page (select-all, copy) into the structured persona: name, title, rate, overview, skills, portfolio, work history with client feedback, employment. Saves it when the persona is empty; otherwise returns the parsed result for review unless overwrite is true. Limited to a few imports per day. (writes)

Parameters:

- `text` (string, required): The full text of the Upwork profile page
- `overwrite` (boolean): Replace an existing persona with the parsed result. Default false.

### Profile

#### get_profile

Get your Vibeworker freelancer profile — skills, niche, scoring mode, and subscribed categories. Useful context before evaluating jobs. (read-only)

No parameters.

#### update_profile

Update the freelancer profile Vibeworker uses to score and rank jobs, and/or the scoring mode. Updating profileText re-embeds your profile and re-matches your recent job history, so this can take a few seconds. (writes)

Parameters:

- `profileText` (string): Free-text description of your skills, niche, rate, and preferences — this is what scoring is matched against
- `scoringMode` ("digest" | "reviewStacking" | "sniper" | "highValue"): digest = balanced default, reviewStacking = optimize for winnable quick jobs, sniper/highValue = optimize for high-value matches

### Filters

#### list_filters

List all of your filter presets, including which one is active for the dashboard and which for notifications. (read-only)

No parameters.

#### create_filter

Create a new filter preset (a saved set of job-matching rules — budget, keywords, categories, etc). This is how you set up Vibeworker's feed and notifications for a user from scratch. (writes)

Parameters:

- `name` (string, required): Display name for this filter, e.g. "High-value React work"
- `categories` (string[]): Upwork categories to match against. Omit or empty = all categories.
- `platforms` (string[]): Non-Upwork platforms to include (Upwork is always included). Omit = Upwork-only.
- `jobType` ("Fixed" | "Hourly"): Restrict to Fixed or Hourly jobs
- `experienceLevel` ("Entry Level" | "Intermediate" | "Expert"): Restrict to a client-stated experience level
- `budgetMin` (number): Minimum budget in USD (applies regardless of job type)
- `budgetMinHourly` (number): Minimum budget in USD, hourly jobs only
- `budgetMinFixed` (number): Minimum budget in USD, fixed-price jobs only
- `hideUnpostedBudget` (boolean): Hide jobs with no budget stated
- `connectsMax` (number): Maximum Upwork connects required to apply
- `requirePaymentVerified` (boolean): Only show jobs from payment-verified clients
- `minClientRating` (number): Minimum client star rating (0-5)
- `minClientSpent` (number): Minimum client lifetime spend in USD
- `minHireRate` (number): Minimum client hire rate, 0-1
- `minHires` (number): Minimum total freelancers the client has hired
- `keywordsInclude` (string[]): At least one of these keywords must appear in title/description
- `keywordsRequire` (string[]): All of these keywords must appear in title/description
- `keywordsExclude` (string[]): Jobs containing any of these keywords are excluded
- `excludeLocations` (string[]): Exclude jobs whose client location contains any of these strings
- `postedWithinHours` (number): Only show jobs posted within this many hours

#### update_filter

Update an existing filter preset. Only the fields you pass are changed — everything else on the filter is left as-is. (writes)

Parameters:

- `id` (string, required): Filter preset id (from list_filters)
- `name` (string): New display name
- `categories` (string[]): Replace the categories this filter matches. Omit to leave unchanged.
- `platforms` (string[]): Replace the non-Upwork platforms included. Omit to leave unchanged.
- `jobType` ("Fixed" | "Hourly"): Restrict to Fixed or Hourly jobs
- `experienceLevel` ("Entry Level" | "Intermediate" | "Expert"): Restrict to a client-stated experience level
- `budgetMin` (number): Minimum budget in USD (applies regardless of job type)
- `budgetMinHourly` (number): Minimum budget in USD, hourly jobs only
- `budgetMinFixed` (number): Minimum budget in USD, fixed-price jobs only
- `hideUnpostedBudget` (boolean): Hide jobs with no budget stated
- `connectsMax` (number): Maximum Upwork connects required to apply
- `requirePaymentVerified` (boolean): Only show jobs from payment-verified clients
- `minClientRating` (number): Minimum client star rating (0-5)
- `minClientSpent` (number): Minimum client lifetime spend in USD
- `minHireRate` (number): Minimum client hire rate, 0-1
- `minHires` (number): Minimum total freelancers the client has hired
- `keywordsInclude` (string[]): At least one of these keywords must appear in title/description
- `keywordsRequire` (string[]): All of these keywords must appear in title/description
- `keywordsExclude` (string[]): Jobs containing any of these keywords are excluded
- `excludeLocations` (string[]): Exclude jobs whose client location contains any of these strings
- `postedWithinHours` (number): Only show jobs posted within this many hours

#### tune_filter

Tighten or loosen a filter's client-quality bar until it lands near a target number of jobs per day, using the last 7 days of jobs in its categories. Same ratchet onboarding uses. Returns the resulting filters; pass apply: true to save them onto the filter. (writes)

Parameters:

- `filterId` (string, required): Filter ID from list_filters
- `targetPerDay` (integer, required): Jobs per day to aim for, e.g. 10
- `apply` (boolean): Save the tuned filters onto the filter. Default false (preview only).

#### assign_filter

Make a filter the active one for either the dashboard feed or notifications. (writes)

Parameters:

- `presetId` (any, required): Filter preset id, or null to unassign
- `surface` ("dashboard" | "notification", required): Which surface to assign this filter to

#### reorder_filters

Reorder your filter presets (affects dashboard tab order). (writes)

Parameters:

- `ids` (string[], required): Filter preset ids in the desired order

#### delete_filter

Permanently delete a filter preset. This cannot be undone. (writes, destructive)

Parameters:

- `id` (string, required): Filter preset id to delete

### Alerts and feeds

#### update_filter_notifications

Configure which notification channels fire for a filter, and the score threshold that triggers them. (writes)

Parameters:

- `id` (string, required): Filter preset id
- `notifyChannels` (object): Only the channel keys you include are changed — other channels already configured on this filter are left as-is. web_push = browser push, push = mobile app push, webhook = POST a signed payload to a URL (requires url when enabling), telegram = Telegram bot, email = email digest.
- `notifyThreshold` (number): Score threshold that triggers a notification (meaning depends on scoring mode)

#### get_telegram_link

Connect Telegram for job alerts. Returns a one-time link the freelancer opens in Telegram to link their account; it expires in 15 minutes. After linking, turn Telegram on for a filter with update_filter_notifications. (writes)

No parameters.

#### get_feed_url

Turn on the private RSS feed for a filter and return its URL, for feed readers, Zapier, Make, or n8n. Returns the existing URL if the feed is already on. Subscribers only. (writes)

Parameters:

- `filterId` (string, required): Filter ID from list_filters

## Notes for assistants

- Nothing here submits to Upwork. The user always applies themselves; get_job and generate_proposal return the Upwork URL to open.
- delete_filter is irreversible. Confirm with the user before calling it.
- Read with get_persona before update_persona: list fields (skills, projects, workHistory, employment) replace the whole list.
- Filter and persona changes are unmetered on every plan, so setting an account up through the assistant costs the user nothing.
