jobctl

Job application tracker for developers - AI agents write over MCP, you review in a dashboard.

사용해야 할까요

품질 및 안전성

A
설명 품질
100%
스키마 완전성
82%
이름 품질
95%
오염 위험
80%
권한 일치
100%
프로토콜 준수
100%

발견 사항 (2)

  • HIGHTool poisoning patterns detected
  • MEDIUMTool description contains URL to non-standard domainlog_interview에서

도구 정의와 프로토콜 준수에 대한 자동 분석을 기반으로 합니다.

컨텍스트 비용

~3,891토큰 (도구 정의)
~2.4 KB일반적인 응답 크기
상당한 주의 영향 (128k 컨텍스트의 3.04%)

이는 서버의 도구가 모델의 컨텍스트에 로드될 때마다 소비되는 대략적인 토큰 수입니다. 수치가 높을수록 다른 작업에 사용할 수 있는 주의가 줄어듭니다.

설치

원클릭 설치

`claude_desktop_config.json` 파일에 다음을 추가하세요:

{
  "mcpServers": {
    "jobctl": {
      "url": "https://app.jobctl.app/mcp"
    }
  }
}

원격 엔드포인트

https://app.jobctl.app/mcpstreamable-http

할 수 있는 일

도구 목록

도구 (11)

🟢 읽기 전용🟡 쓰기🔴 삭제⚪ 알 수 없음
🟡add_application(raw, company, role, status, priority, ...)

Track a new job application. Pass either a single `raw` line — `add_application({raw: "Bosch, Tech Lead, prio A, due friday, remote, via LinkedIn"})` — or structured fields (`company` and `role` required then). Returns the created application and possible-duplicate warnings; if duplicates are listed, tell the user instead of creating again.

입력 스키마

{
  "type": "object",
  "properties": {
    "raw": {
      "description": "Free-form line: \"Company, Role, prio A|B|C, due <date>, remote|hybrid|onsite, via <source>, https://…, in <location>, applied <date>\"",
      "type": "string"
    },
    "company": {
      "type": "string"
    },
    "role": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "started_apply",
        "waiting_referral",
        "applied",
        "waiting",
        "interview",
        "offer",
        "accepted",
        "rejected",
        "no_response",
        "on_hold",
        "withdrawn",
        "archived"
      ]
    },
    "priority": {
      "description": "A = top priority",
      "type": "string",
      "enum": [
        "A",
        "B",
        "C"
      ]
    },
    "remote": {
      "type": "string",
      "enum": [
        "remote",
        "hybrid",
        "onsite",
        "unknown"
      ]
    },
    "remote_note": {
      "description": "e.g. \"2 days office\"",
      "type": "string"
    },
    "source": {
      "description": "LinkedIn, referral, recruiter, Easy Apply…",
      "type": "string"
    },
    "url": {
      "description": "Link to the job posting",
      "type": "string"
    },
    "location": {
      "type": "string"
    },
    "salary_asked": {
      "description": "What the candidate asked for — yearly gross EUR as a plain number, e.g. 110000",
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "salary_range_min": {
      "description": "Posting salary range lower bound — yearly gross EUR, plain number",
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "salary_range": {
      "description": "Posting salary range upper bound — yearly gross EUR, plain number",
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "cv_variant": {
      "description": "Which CV was sent: DE, EN, AI-focused…",
      "type": "string"
    },
    "match_notes": {
      "description": "Markdown match assessment: why the candidate fits this role — strengths vs requirements, gaps, level/salary advice, process notes. Save your vacancy analysis here so it is not lost; shown as an expandable section in the dashboard.",
      "type": "string"
    },
    "next_interview_at": {
      "description": "ISO datetime, e.g. 2026-07-20T14:00:00+02:00",
      "type": "string"
    },
    "next_action": {
      "description": "e.g. \"follow-up\", \"thank-you email\"",
      "type": "string"
    },
    "next_action_due": {
      "description": "ISO date YYYY-MM-DD",
      "type": "string"
    },
    "applied_at": {
      "description": "ISO date YYYY-MM-DD",
      "type": "string"
    }
  },
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🟢list_applications(view, status, priority, q, limit)

List job applications. Filter by view (active = default working set, archive = rejected/withdrawn/no-response), status list, priority, or free-text search in company/role. Example: `list_applications({view: "active"})`, `list_applications({q: "bosch"})`.

입력 스키마

{
  "type": "object",
  "properties": {
    "view": {
      "type": "string",
      "enum": [
        "active",
        "interviewing",
        "waiting",
        "archive",
        "all"
      ]
    },
    "status": {
      "description": "Comma-separated: started_apply, waiting_referral, applied, waiting, interview, offer, accepted, rejected, no_response, on_hold, withdrawn, archived",
      "type": "string"
    },
    "priority": {
      "type": "string",
      "enum": [
        "A",
        "B",
        "C"
      ]
    },
    "q": {
      "description": "Search in company/role",
      "type": "string"
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 500
    }
  },
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🟡update_application(id, company, role, status, priority, ...)

Update fields of an application (get the id from list_applications). Status changes are logged to the timeline automatically. Example: `update_application({id: "…", status: "interview", next_action: "prepare system design", next_action_due: "2026-07-20"})`.

입력 스키마

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "company": {
      "type": "string"
    },
    "role": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "started_apply",
        "waiting_referral",
        "applied",
        "waiting",
        "interview",
        "offer",
        "accepted",
        "rejected",
        "no_response",
        "on_hold",
        "withdrawn",
        "archived"
      ]
    },
    "priority": {
      "description": "A = top priority",
      "type": "string",
      "enum": [
        "A",
        "B",
        "C"
      ]
    },
    "remote": {
      "type": "string",
      "enum": [
        "remote",
        "hybrid",
        "onsite",
        "unknown"
      ]
    },
    "remote_note": {
      "description": "e.g. \"2 days office\"",
      "type": "string"
    },
    "source": {
      "description": "LinkedIn, referral, recruiter, Easy Apply…",
      "type": "string"
    },
    "url": {
      "description": "Link to the job posting",
      "type": "string"
    },
    "location": {
      "type": "string"
    },
    "salary_asked": {
      "description": "What the candidate asked for — yearly gross EUR as a plain number, e.g. 110000",
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "salary_range_min": {
      "description": "Posting salary range lower bound — yearly gross EUR, plain number",
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "salary_range": {
      "description": "Posting salary range upper bound — yearly gross EUR, plain number",
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "cv_variant": {
      "description": "Which CV was sent: DE, EN, AI-focused…",
      "type": "string"
    },
    "match_notes": {
      "description": "Markdown match assessment: why the candidate fits this role — strengths vs requirements, gaps, level/salary advice, process notes. Save your vacancy analysis here so it is not lost; shown as an expandable section in the dashboard.",
      "type": "string"
    },
    "next_interview_at": {
      "description": "ISO datetime, e.g. 2026-07-20T14:00:00+02:00",
      "type": "string"
    },
    "next_action": {
      "description": "e.g. \"follow-up\", \"thank-you email\"",
      "type": "string"
    },
    "next_action_due": {
      "description": "ISO date YYYY-MM-DD",
      "type": "string"
    },
    "applied_at": {
      "description": "ISO date YYYY-MM-DD",
      "type": "string"
    }
  },
  "required": [
    "id"
  ],
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🟡add_note(application_id, text)

Append a free-text note to an application timeline — recruiter call summaries, interview instructions, pasted messages. Multiline markdown is fine. Example: `add_note({application_id: "…", text: "Recruiter: team of 5, on-call rotation, offer range 85-95k"})`.

입력 스키마

{
  "type": "object",
  "properties": {
    "application_id": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "text": {
      "type": "string",
      "minLength": 1
    }
  },
  "required": [
    "application_id",
    "text"
  ],
  "$schema": "http://json-schema.org/draft-07/schema#"
}
⚪log_interview(application_id, scheduled_at, status, attendees, links, ...)

Log an interview for an application (max 10 per application): when it takes place, its status (planned or done), who attends (each with an optional profile link), related links (prep materials the company sent, meeting URL), and a short recap of what was discussed. Also keeps the application's next-interview date in sync. Example: `log_interview({application_id: "…", scheduled_at: "2026-07-20T14:00:00+02:00", status: "planned", attendees: [{name: "Anna Weber (HR)", url: "https://linkedin.com/in/annaweber"}], links: [{label: "Prep materials", url: "https://company.com/interview-prep"}], recap: "System design round"})`.

입력 스키마

{
  "type": "object",
  "properties": {
    "application_id": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "scheduled_at": {
      "type": "string",
      "description": "ISO datetime with offset, e.g. 2026-07-20T14:00:00+02:00"
    },
    "status": {
      "description": "planned (default) or done",
      "type": "string",
      "enum": [
        "planned",
        "done"
      ]
    },
    "attendees": {
      "description": "Who attends, each with an optional profile link",
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "url": {
            "description": "Profile link, e.g. LinkedIn",
            "type": "string"
          }
        },
        "required": [
          "name"
        ]
      }
    },
    "links": {
      "description": "Related links: prep materials, meeting URL, docs",
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "label": {
            "description": "e.g. \"Prep materials\", \"Meeting link\"",
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "url"
        ]
      }
    },
    "recap": {
      "description": "Short recap of what was discussed",
      "type": "string"
    }
  },
  "required": [
    "application_id",
    "scheduled_at"
  ],
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🟢get_due

What is burning today: applications with next_action_due today or overdue. Call this when the user asks "what should I do today?" about their job search.

입력 스키마

{
  "type": "object",
  "properties": {},
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🟢get_planned(range)

What is actually SCHEDULED: interviews with their time, attendees and meeting links, plus the actions falling due, for today, tomorrow, or the next seven days. Use this whenever the user asks what is planned, what the day or week looks like, or before proposing a time to anyone — get_due only knows action deadlines and is blind to booked interviews. Example: `get_planned({range: "tomorrow"})`.

입력 스키마

{
  "type": "object",
  "properties": {
    "range": {
      "description": "today (default), tomorrow, or week (today plus the next 6 days)",
      "type": "string",
      "enum": [
        "today",
        "tomorrow",
        "week"
      ]
    }
  },
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🔴attach_analysis(application_id, markdown)

Attach a markdown company analysis (research you produced) to an application. Single document per application — a new call REPLACES the previous one. Max 100000 bytes (~100 KB) of utf-8. The user reads it in the dashboard, e.g. before an interview. Example: `attach_analysis({application_id: "…", markdown: "# Bosch\n\n## Culture…"})`.

입력 스키마

{
  "type": "object",
  "properties": {
    "application_id": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "markdown": {
      "type": "string",
      "minLength": 1,
      "description": "The analysis as markdown"
    }
  },
  "required": [
    "application_id",
    "markdown"
  ],
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🔴attach_file(application_id, kind, filename, data_base64, mime)

Attach or replace a file on an application so the user sees it in the dashboard: a CV/resume (kind: 'cv') or a cover letter (kind: 'cover_letter'). Pass the file bytes as base64 in data_base64. One file per (application, kind) - a new call REPLACES the previous one. Max ~5 MB; pdf/doc/docx/txt/md (MIME is inferred from the filename if you omit it). Call this whenever you generate a tailored CV or cover letter for an application. Example: `attach_file({application_id: "…", kind: "cv", filename: "CV_Cursor.pdf", data_base64: "JVBERi0…"})`.

입력 스키마

{
  "type": "object",
  "properties": {
    "application_id": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "kind": {
      "type": "string",
      "enum": [
        "cv",
        "cover_letter"
      ]
    },
    "filename": {
      "type": "string",
      "minLength": 1,
      "maxLength": 300
    },
    "data_base64": {
      "type": "string",
      "minLength": 1,
      "description": "Base64-encoded file bytes"
    },
    "mime": {
      "description": "Optional; inferred from the filename extension when omitted",
      "type": "string"
    }
  },
  "required": [
    "application_id",
    "kind",
    "filename",
    "data_base64"
  ],
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🟢attach_interview_file(application_id, interview_id, filename, data_base64, mime)

Attach prep material to a specific interview so the user sees it next to that round in the dashboard: the prep document you wrote, the job description, a deck, notes the company sent. Max 3 files per interview, 20000000 bytes (20 MB) each; pass the bytes as base64. Get interview ids from get_application. Example: `attach_interview_file({application_id: "…", interview_id: "…", filename: "prep-round2.md", data_base64: "IyBQcmVw…"})`.

입력 스키마

{
  "type": "object",
  "properties": {
    "application_id": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "interview_id": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "filename": {
      "type": "string",
      "minLength": 1,
      "maxLength": 300
    },
    "data_base64": {
      "type": "string",
      "minLength": 1,
      "description": "Base64-encoded file bytes"
    },
    "mime": {
      "description": "Optional; inferred from the filename extension when omitted",
      "type": "string"
    }
  },
  "required": [
    "application_id",
    "interview_id",
    "filename",
    "data_base64"
  ],
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🟢get_application(id)

Full detail of one application: all fields, contacts, recent timeline events, and whether a company analysis is attached (fetch it via the dashboard or attach_analysis to replace).

입력 스키마

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    }
  },
  "required": [
    "id"
  ],
  "$schema": "http://json-schema.org/draft-07/schema#"
}

커뮤니티

이 서버 평가하기

증거

최근 관측

검증됨버전이 기록되지 않음도구 11개
검증됨버전이 기록되지 않음도구 11개
검증됨버전이 기록되지 않음도구 11개