@chinchillaenterprises/mcp-upwork
Version:
Modular Upwork MCP server for job search and proposal management via Upwork API
76 lines (68 loc) • 3.82 kB
text/typescript
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
};