Skip to main content
Glama
WonderCV

ClawHire MCP

by WonderCV

post_job

Publish job listings to the ClawHire hiring platform to reach candidates through search results, matching recommendations, and WonderCV channels.

Instructions

发布职位到 ClawHire 招聘市场。

职位将同时出现在:

  1. ClawHire 雇主端搜索结果

  2. 候选人的匹配推荐(如果符合其期望)

  3. WonderCV 相关渠道

提示:

  • 设置 require_ai_fluency=true 可优先展示给 AI-Agent 熟练的候选人

  • 工作年限会自动转换为天数存储(WonderCV 内部格式)

  • 职位默认有效期30天

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
session_idYes会话 ID,从 register_company 获取
job_postYes职位名称,如:高级产品经理、AI算法工程师
jdYes职位描述(JD),包括岗位职责、任职要求等
city_namesYes工作城市列表,如:["上海"] 或 ["北京", "上海"]
salary_minNo最低月薪(人民币),如:30000
salary_maxNo最高月薪(人民币),如:50000
experience_minNo最低工作年限(年),如:3
experience_maxNo最高工作年限(年),如:5
degree_nameNo学历要求
job_natureNo工作性质,如:全职、兼职、实习
job_tagsNo职位标签,如:["AI", "远程", "股权激励"]
profession_idNo职位分类ID(WonderCV 内部编码)
require_ai_fluencyNo是否优先考虑AI Agent熟练的候选人

Implementation Reference

  • The main execute function for the post_job tool. Validates session, checks quota, calls backend API to post the job, increments usage counter, and returns success/error response with job details.
    async execute(input: Input) {
      // Validate session
      const session = getSession(input.session_id);
      if (!session) {
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                error: {
                  code: 'INVALID_SESSION',
                  message: '会话已过期或无效,请重新调用 register_company',
                },
              }, null, 2),
            },
          ],
          isError: true,
        };
      }
    
      // Check quota (preview only - don't consume yet)
      // TODO: Fix race condition - concurrent requests can both pass this check
      // See KnownIssues.md #1 - needs reservation/commit pattern or backend metering
      const remainingBefore = getRemainingQuota(session, 'jobs_posted');
      if (remainingBefore <= 0) {
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                error: {
                  code: 'QUOTA_EXCEEDED',
                  message: `今日职位发布额度已用完`,
                },
                access: {
                  tier: session.tier,
                  reset_at: '次日零点',
                },
              }, null, 2),
            },
          ],
          isError: true,
        };
      }
    
      try {
        const response = await postJob({
          session_id: input.session_id,
          job_post: input.job_post,
          jd: input.jd,
          city_names: input.city_names,
          salary_min: input.salary_min,
          salary_max: input.salary_max,
          experience_min: input.experience_min,
          experience_max: input.experience_max,
          degree_name: input.degree_name,
          job_nature: input.job_nature,
          job_tags: input.job_tags,
          profession_id: input.profession_id,
          require_ai_fluency: input.require_ai_fluency,
        });
    
        const { data } = response;
    
        // Consume quota only after successful API call
        checkAndIncrementUsage(session, 'jobs_posted');
    
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                data: {
                  job_token: data.job_token,
                  job_post: data.job_post,
                  status: data.status,
                  status_text: '已发布',
                  expires_at: data.expires_at,
                  tip: data.tip,
                },
                access: {
                  tier: session.tier,
                  jobs_remaining_today: getRemainingQuota(session, 'jobs_posted'),
                },
                meta: {
                  session_id: session.session_id,
                },
                next_steps: [
                  `调用 search_candidates 为 "${data.job_post}" 搜索匹配候选人`,
                  `调用 list_jobs 查看所有已发布的职位`,
                ],
              }, null, 2),
            },
          ],
          isError: false,
        };
      } catch (error) {
        const errorMessage = error instanceof Error ? error.message : '发布失败';
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                error: {
                  code: 'POST_JOB_FAILED',
                  message: errorMessage,
                },
              }, null, 2),
            },
          ],
          isError: true,
        };
      }
    },
  • Zod input schema defining all parameters for posting a job: session_id, job_post, jd, city_names, salary range, experience range, degree, job_nature, job_tags, profession_id, and require_ai_fluency. Includes validation rules and descriptions.
    const inputSchema = z.object({
      session_id: z.string()
        .describe('会话 ID,从 register_company 获取'),
      
      job_post: z.string()
        .min(2, '职位名称至少需要2个字符')
        .max(100, '职位名称不能超过100个字符')
        .describe('职位名称,如:高级产品经理、AI算法工程师'),
      
      jd: z.string()
        .min(50, '职位描述至少需要50个字符')
        .max(10000, '职位描述不能超过10000个字符')
        .describe('职位描述(JD),包括岗位职责、任职要求等'),
      
      city_names: z.array(z.string())
        .min(1, '请至少填写一个城市')
        .describe('工作城市列表,如:["上海"] 或 ["北京", "上海"]'),
      
      salary_min: z.number()
        .int()
        .min(1000)
        .optional()
        .describe('最低月薪(人民币),如:30000'),
      
      salary_max: z.number()
        .int()
        .min(1000)
        .optional()
        .describe('最高月薪(人民币),如:50000'),
      
      experience_min: z.number()
        .min(0)
        .max(50)
        .optional()
        .describe('最低工作年限(年),如:3'),
      
      experience_max: z.number()
        .min(0)
        .max(50)
        .optional()
        .describe('最高工作年限(年),如:5'),
      
      degree_name: z.enum(['大专', '本科', '硕士', '博士'])
        .optional()
        .describe('学历要求'),
      
      job_nature: z.string()
        .optional()
        .describe('工作性质,如:全职、兼职、实习'),
      
      job_tags: z.array(z.string())
        .optional()
        .describe('职位标签,如:["AI", "远程", "股权激励"]'),
      
      profession_id: z.number()
        .int()
        .positive()
        .optional()
        .describe('职位分类ID(WonderCV 内部编码)'),
      
      require_ai_fluency: z.boolean()
        .optional()
        .describe('是否优先考虑AI Agent熟练的候选人'),
    });
  • Tool registration: imports postJobTool from post_job.js, exports it, and adds it to the allTools registry array which is used to initialize the MCP server.
    import { postJobTool } from './post_job.js';
    import { listJobsTool } from './list_jobs.js';
    import { searchCandidatesTool } from './search_candidates.js';
    import { viewCandidateTool } from './view_candidate.js';
    import { listApplicationsTool } from './list_applications.js';
    import { requestOutreachTool } from './request_outreach.js';
    
    // Export all tools
    export {
      registerCompanyTool,
      postJobTool,
      listJobsTool,
      searchCandidatesTool,
      viewCandidateTool,
      listApplicationsTool,
      requestOutreachTool,
    };
    
    // Tool registry for server initialization
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    export const allTools: Tool<any>[] = [
      registerCompanyTool,
      postJobTool,
      listJobsTool,
      searchCandidatesTool,
      viewCandidateTool,
      listApplicationsTool,
      requestOutreachTool,
    ];
  • Backend API helper function that posts the job to the WonderCV API. Converts experience from years to days (WonderCV convention) and makes the HTTP POST request to /jobs endpoint.
    export async function postJob(
      input: PostJobInput
    ): Promise<ApiResponse<PostJobOutput>> {
      // Convert experience from years to days for WonderCV backend
      const body: Record<string, unknown> = {
        ...input,
      };
      
      if (input.experience_min !== undefined) {
        body.experience_min = yearsToDays(input.experience_min);
      }
      if (input.experience_max !== undefined) {
        body.experience_max = yearsToDays(input.experience_max);
      }
    
      return apiRequest('/jobs', {
        method: 'POST',
        body,
      });
    }
  • TypeScript interfaces defining the input and output types for the backend API postJob function. PostJobInput matches the tool input, PostJobOutput contains job_token, job_post, status, expires_at, and tip.
    export interface PostJobInput {
      session_id: string;
      job_post: string;
      jd: string;
      city_names: string[];
      salary_min?: number;
      salary_max?: number;
      experience_min?: number;  // Years - will be converted to days
      experience_max?: number;  // Years - will be converted to days
      degree_name?: string;
      job_nature?: string;
      job_tags?: string[];
      profession_id?: number;
      require_ai_fluency?: boolean;
    }
    
    export interface PostJobOutput {
      job_token: string;
      job_post: string;
      status: number;
      expires_at: string;
      tip: string;
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/WonderCV/clawhire-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server