import { Tool } from "@modelcontextprotocol/sdk/types.js"; import { UpworkClient } from "../../services/upwork-client.js"; import { z } from "zod"; // Schema for tool arguments const GetLatestJobsArgsSchema = z.object({ limit: z.number().min(1).max(100).default(30).describe("Number of jobs to return"), searchTerm: z.string().optional().describe("Optional search term to filter jobs") }); export const getLatestJobsToolDefinition: Tool = { name: "upwork_get_latest_jobs", description: "Get the latest jobs from Upwork using GraphQL, sorted by recency", inputSchema: { type: "object", properties: { limit: { type: "number", minimum: 1, maximum: 100, default: 30, description: "Number of jobs to return" }, searchTerm: { type: "string", description: "Optional search term to filter jobs" } } } }; export async function getLatestJobsHandler(args: unknown, upworkClient: UpworkClient) { const validatedArgs = GetLatestJobsArgsSchema.parse(args); try { let query: string; let variables: any = {}; if (validatedArgs.searchTerm) { query = ` query GetLatestJobs($searchTerm: String!) { marketplaceJobPostings( marketPlaceJobFilter: { searchExpression_eq: $searchTerm } searchType: USER_JOBS_SEARCH sortAttributes: { field: RECENCY } ) { totalCount edges { node { id title description createdDateTime skills { name } experienceLevel duration engagement freelancersToHire } } } } `; variables = { searchTerm: validatedArgs.searchTerm }; } else { query = ` query GetLatestJobs { marketplaceJobPostings( searchType: USER_JOBS_SEARCH sortAttributes: { field: RECENCY } ) { totalCount edges { node { id title description createdDateTime skills { name } experienceLevel duration engagement freelancersToHire } } } } `; } const response = await upworkClient.graphqlQuery(query, variables); const jobs = response.data?.marketplaceJobPostings?.edges || []; const limitedJobs = jobs.slice(0, validatedArgs.limit); // Format jobs for better readability const formattedJobs = limitedJobs.map((edge: any, index: number) => { const job = edge.node; const skills = job.skills?.map((s: any) => s.name).join(", ") || "No skills listed"; return { index: index + 1, id: job.id, title: job.title, posted: job.createdDateTime, experienceLevel: job.experienceLevel || "Not specified", duration: job.duration || "Not specified", engagement: job.engagement || "Not specified", freelancersToHire: job.freelancersToHire || 1, skills: skills, description: job.description?.substring(0, 200) + "..." || "No description" }; }); return { content: [ { type: "text", text: JSON.stringify({ success: true, totalCount: response.data?.marketplaceJobPostings?.totalCount || 0, jobsReturned: formattedJobs.length, searchTerm: validatedArgs.searchTerm || null, jobs: formattedJobs }, null, 2) } ] }; } catch (error) { throw upworkClient.handleError(error); } } export const getLatestJobsTool = { definition: getLatestJobsToolDefinition, handler: getLatestJobsHandler, schema: GetLatestJobsArgsSchema };