UNPKG

@chinchillaenterprises/mcp-upwork

Version:

Modular Upwork MCP server for job search and proposal management via Upwork API

76 lines (68 loc) 3.82 kB
import { Tool } from "@modelcontextprotocol/sdk/types.js"; import { SearchJobsArgsSchema, SearchJobsArgs } from "../../schemas/upwork.js"; import { UpworkClient } from "../../services/upwork-client.js"; export const searchJobsToolDefinition: Tool = { name: "upwork_search_jobs", description: "Search for jobs on Upwork with various filters", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query for job titles/descriptions" }, category: { type: "string", description: "Job category filter" }, skills: { type: "array", items: { type: "string" }, description: "Required skills" }, budget_min: { type: "number", description: "Minimum budget" }, budget_max: { type: "number", description: "Maximum budget" }, hourly_rate_min: { type: "number", description: "Minimum hourly rate" }, hourly_rate_max: { type: "number", description: "Maximum hourly rate" }, duration: { type: "string", enum: ["1-7 days", "1-4 weeks", "1-3 months", "3-6 months", "6+ months"], description: "Project duration" }, workload: { type: "string", enum: ["less-than-30", "30-plus"], description: "Hours per week" }, client_hires: { type: "number", description: "Minimum client hire rate percentage" }, client_spent: { type: "number", description: "Minimum client total spent" }, sort: { type: "string", enum: ["recency", "client_rating", "payment_verified"], description: "Sort order" }, page: { type: "number", minimum: 1, default: 1, description: "Page number for pagination" }, limit: { type: "number", minimum: 1, maximum: 100, default: 20, description: "Number of results per page" } } } }; export async function searchJobsHandler(args: unknown, upworkClient: UpworkClient) { const validatedArgs = SearchJobsArgsSchema.parse(args); try { const params = new URLSearchParams(); if (validatedArgs.query) params.append('q', validatedArgs.query); if (validatedArgs.category) params.append('category2', validatedArgs.category); if (validatedArgs.skills?.length) params.append('skills', validatedArgs.skills.join(';')); if (validatedArgs.budget_min) params.append('budget_min', validatedArgs.budget_min.toString()); if (validatedArgs.budget_max) params.append('budget_max', validatedArgs.budget_max.toString()); if (validatedArgs.hourly_rate_min) params.append('hourly_rate_min', validatedArgs.hourly_rate_min.toString()); if (validatedArgs.hourly_rate_max) params.append('hourly_rate_max', validatedArgs.hourly_rate_max.toString()); if (validatedArgs.duration) params.append('duration', validatedArgs.duration); if (validatedArgs.workload) params.append('workload', validatedArgs.workload); if (validatedArgs.client_hires) params.append('client_hires', validatedArgs.client_hires.toString()); if (validatedArgs.client_spent) params.append('client_spent', validatedArgs.client_spent.toString()); if (validatedArgs.sort) params.append('sort', validatedArgs.sort); params.append('page', validatedArgs.page.toString()); params.append('count', validatedArgs.limit.toString()); const response = await upworkClient.getClient().get(`/profiles/v1/search/jobs?${params.toString()}`); return { content: [ { type: "text", text: JSON.stringify({ success: true, jobs: response.data.jobs || [], total: response.data.total || 0, page: validatedArgs.page, limit: validatedArgs.limit }, null, 2) } ] }; } catch (error) { throw upworkClient.handleError(error); } } export const searchJobsTool = { definition: searchJobsToolDefinition, handler: searchJobsHandler, schema: SearchJobsArgsSchema };