Skip to main content
Glama
clement-igonet

apec-mcp

APEC MCP Server

MCP (Model Context Protocol) server that exposes the APEC candidate-profile API as tools for any MCP-compatible AI assistant.


API Analysis

API Type

Proprietary REST / JSON over HTTPS.
Not publicly documented. Discovered via browser network capture (HAR files).
Base URL: https://www.apec.fr/cms/webservices/

Authentication

Email/password auto-login — no manual cookie handling required.

The server posts credentials to /.apec-login.do at first use and caches the resulting session cookies in ~/.apec_session.json. On 401/403 the session is automatically invalidated and a fresh login is attempted before retrying.

Env var

Purpose

APEC_EMAIL

APEC account email

APEC_PASSWORD

APEC account password

APEC_ID_COMPTE_CADRE

Numeric account ID (from your APEC profile URL)

APEC_ID_PROFIL_CADRE

Numeric profile ID (from your APEC profile URL)

APEC_SESSION_FILE

Override session cache path (default: ~/.apec_session.json)

APEC_COOKIES

Manual cookie override — takes precedence over auto-login (fallback if DataDome blocks login)

Reflection / Introspection

There is no public API schema (no OpenAPI spec, no GraphQL introspection).
The API was reverse-engineered from three browser sessions:

  • www.apec.fr.har — initial profile creation

  • www2.apec.fr.har — portfolio/strengths editing

  • www3.apec.fr.har — full profile editing (business card, photo upload, reference data)

The backend appears to be a Java Spring application (JSESSIONID, Jahia CMS static assets).


Related MCP server: MCP Employee Identity Server

API Endpoints

Profile

Method

Path

Description

GET

/profilCadre/{idCompte}/complete

Full profile (the canonical object)

POST

/profilCadre/updateExperiencesEtFormations

Save experiences + education

POST

/profilCadre/updateInformationsComplementaires

Save job-search preferences

POST

/profilCadre/updateAtouts

Save skills + strengths

POST

/profilCadre/carteDeVisite

Save personal / contact info

POST

/profilCadre/validation/{SECTION}

Validate before saving

GET

/profilCadre/shouldDisplayEncart

UI display flag

GET

/profilCadre/photoUploadFolders

Photo upload paths

Stats

Method

Path

Description

GET

/profilVu/{idProfil}/nombreVuesDepuisPublication

Total profile views

GET

/profilVu/{idProfil}/dureePublication

Days since publication

GET

/profilVu/{idProfil}/tendanceVues

Views trend (float)

POST

/echangeProfil/count

Unread recruiter messages

POST

/panierProfil/countInterlocuteursRetenir

Saved contacts count

Search / Autocomplete

Method

Path

Description

GET

/autocompletion/distinctMetierAutocomplete

Job-title search

GET

/autocompletion/autocomplete

General search

Reference Data

Method

Path

Description

POST

/referentielstatique/presentations/visuels/liste/hierarchie

Enum lists (869 entries)

GET

/referentielstatique/fonctions/metiers/{id}/organisation

Métier taxonomy node

GET

/referentielstatique/presentations/code/LISTE_MONDE/visuels

Country list

Identification & CV

Method

Path

Description

GET

/identification/apecuser

Current user info

GET

/cv/cvFichiersByIdCompte

Uploaded CV files

GET

/pushRecoOffre/abonnement/cadre/{id}

Job-recommendation subscription

Job Offers

Method

Path

Description

POST

/affiniteOrchestration/matching-candidat

AI-matched offers for the candidate

GET

/offre/public

Full offer details by numeroOffre

POST

/rechercheAffinite/rebond

Similar/related offers

GET

/pageEntreprise/public/{id}

Public company profile

Applications & Saved Jobs

Method

Path

Description

POST

/candidature/siteRh

Submit an APEC-hosted application

POST

/candidature/liste

List past applications

POST

/candidature/liste/count

Check if already applied to an offer

POST

/panierOffre/liste

List saved/bookmarked offers


Data Model (TypeSpec)

import "@typespec/rest";
import "@typespec/http";

using TypeSpec.Rest;
using TypeSpec.Http;

// ── Shared ──────────────────────────────────────────────────────────────

model Audit {
  dateCreation: int64;        // epoch ms
  dateModification: int64 | null;
  utilisateurCreation: string;
  utilisateurModification: string | null;
}

// ── Reference / enum entries ─────────────────────────────────────────────

model ReferenceItem {
  codePresentation: string;
  idNomenclature: int64;
  codeNomenclature: string;
  idOrganisation: int64;
  idOrganisationParent: int64 | null;
  libelle: string;
  niveau: int32;
  ordre: int32;
}

// ── Sub-models ───────────────────────────────────────────────────────────

model Experience {
  intitulePoste: string;
  idNomFonction: int64;
  idNomMetier: int64;
  dateEntree: string;           // ISO 8601
  dateSortie: string | null;
  nomEntreprise: string;
  expNonCadre: boolean;
  numeroOrdre: int32;
  audit: Audit;
}

model Formation {
  intituleFormation: string;
  dateEntree: string;           // ISO 8601
  dateSortie: string | null;
  idNomDiscipline: string;
  idNomNiveau: string;
  organismeFormation: string | null;
  numeroOrdre: int32;
  audit: Audit;
}

enum CompetenceType {
  LANGUE,
  SAVOIR_ETRE,
  TECHNIQUE,
  METIER,
}

model Competence {
  libelle: string;
  type: CompetenceType;
  idNomCompetence: int64;
  idNomNiveau: int64 | null;
  miseEnAvant: boolean;
  idProfilCadre: int64;
  audit: Audit;
  id: int64;
}

model AdressePostale {
  adresseNumeroEtVoie: string;
  adresseCodePostal: string;
  adresseVille: string;
  adresseBatimentImmResidence: string | null;
  adresseComplementAdresse: string | null;
  idPays: int64;
  audit: Audit;
  id: int64;
}

model SouhaitSecteur   { idNomSecteurActivite: int64; audit: Audit; id: int64; }
model SouhaitFonction  { idNomFonction: int64; idNomMetier: int64; audit: Audit; id: int64; }
model SouhaitContrat   { idNomTypeContrat: int64; audit: Audit; id: int64; }
model SouhaitEntreprise{ idNomTailleEntreprise: int64; audit: Audit; id: int64; }
model SouhaitTemps     { idNomTempsTravail: int64; audit: Audit; id: int64; }
model SouhaitMode      { idNomModeTravail: int64; audit: Audit; id: int64; }
model SouhaitLieu      { idNomLieu: int64; distance: int32 | null; audit: Audit; id: int64; }

// ── Full profile ─────────────────────────────────────────────────────────

model ProfilCadre {
  id: int64;
  idProfilCadre: int64;
  idCompteCadre: int64;
  idNomStatut: int64;
  idNomStatutCandidat: int64;

  // Personal info
  idNomCivilite: int64;
  nom: string;
  prenom: string;
  dateNaissance: int64;           // epoch ms
  adressePostale: AdressePostale;
  adresseEmail: string;
  numeroTelephoneMobile: string;
  lienLinkedin: string | null;
  photo: string | null;

  // Professional summary
  metierSouhaite: string;
  objectifProfessionnel: string | null;
  pointsClesProfessionnels: string | null;
  idNomAnneesExperience: int64;
  idNomDelaiDisponibilite: int64;
  remunerationMinimale: float32;
  indicateurMasquerSalaire: boolean;
  pretPourRecrutement: boolean;

  // Preferences
  souhaitsSecteurs: SouhaitSecteur[];
  souhaitsFonctions: SouhaitFonction[];
  souhaitsContrats: SouhaitContrat[];
  souhaitsEnts: SouhaitEntreprise[];
  souhaitsTemps: SouhaitTemps[];
  souhaitsModes: SouhaitMode[];
  souhaitsLieux: SouhaitLieu[];

  // Experiences & formations
  experiencesCles: Experience[];
  formationsCles: Formation[];

  // Skills
  competences: Competence[];
  atouts: string[];
  portfolios: unknown[];

  // CV
  idCvFichier: int64 | null;

  // Completion indicators
  tauxRemplissage: int32;
  indicateurCompletCompetence: boolean;
  indicateurCompletCompMea: boolean;
  indicateurCompletPortfolio: boolean;

  // Versioning
  numeroVersion: int32;
  numeroVersionCadre: int32;
  audit: Audit;
  auditCadre: Audit;
}

// ── User identity ────────────────────────────────────────────────────────

model ApecUser {
  id: string;
  numeroCompte: string;
  nom: string;
  prenom: string;
  email: string;
  cadre: boolean;
  actif: boolean;
  token: string;
  sessionId: string;
}

MCP Tools

Tool

APEC endpoint

Description

get_user_info

GET /identification/apecuser

Authenticated user details

get_profile

GET /profilCadre/{id}/complete

Full profile (use as base for updates)

get_profile_stats

GET /profilVu/… × 3

Views, duration, trend

get_cv_files

GET /cv/cvFichiersByIdCompte

Uploaded CVs

get_unread_messages_count

POST /echangeProfil/count

Unread recruiter messages

validate_section

POST /profilCadre/validation/{SECTION}

Validate before saving

update_experiences_formations

POST /profilCadre/updateExperiencesEtFormations

Experiences + education

update_informations_complementaires

POST /profilCadre/updateInformationsComplementaires

Job-search preferences

update_atouts

POST /profilCadre/updateAtouts

Skills + strengths

update_carte_de_visite

POST /profilCadre/carteDeVisite

Personal / contact info

search_metiers

GET /autocompletion/distinctMetierAutocomplete

Job-title autocomplete

autocomplete

GET /autocompletion/autocomplete

General search

get_metier_organisation

GET /referentielstatique/fonctions/…

Métier taxonomy

get_reference_lists

POST /referentielstatique/…/hierarchie

Enum reference data

get_matching_jobs

POST /affiniteOrchestration/matching-candidat

AI-matched job offers (paginated)

get_job_offer

GET /offre/public

Full offer details

get_similar_jobs

POST /rechercheAffinite/rebond

Related offers

get_company_info

GET /pageEntreprise/public/{id}

Public company profile

apply_to_job

POST /candidature/siteRh

Submit APEC-hosted application

list_applications

POST /candidature/liste

Past applications (paginated)

check_applied_to_offer

POST /candidature/liste/count

Check if already applied

list_saved_jobs

POST /panierOffre/liste

Saved/bookmarked offers

refresh_session

POST /.apec-login.do

Force re-login and return user info

Update pattern

All update endpoints require the complete profile object (not a partial patch).

profile = get_profile()
profile["experiencesCles"].append({...})
update_experiences_formations(profile)

Key Use Case — Fill Profile from LinkedIn or CV

The intended end-to-end workflow:

1. [LinkedIn MCP] get_my_profile()           → raw LinkedIn data
   OR parse CV text (PDF / plain text)

2. [APEC MCP]     get_reference_lists([…])  → resolve enum IDs
                  search_metiers(q=…)        → find idNomMetier / idNomFonction

3. AI assistant maps fields:
     LinkedIn title      → metierSouhaite + experiencesCles[].intitulePoste
     LinkedIn positions  → experiencesCles[]
     LinkedIn education  → formationsCles[]
     LinkedIn skills     → competences[]
     LinkedIn location   → adressePostale + souhaitsLieux[]

4. [APEC MCP]     get_profile()                     → fetch current profile
                  update_experiences_formations(…)  → save
                  update_informations_complementaires(…)
                  update_atouts(…)
                  update_carte_de_visite(…)

Setup & Run

Prerequisites

  • uv installed

Install

git clone <repo>
cd apec-mcp
uv sync
cp .env.example .env
# Fill in APEC_EMAIL, APEC_PASSWORD, APEC_ID_COMPTE_CADRE, APEC_ID_PROFIL_CADRE

Local development (SSE)

uv run apec-mcp               # SSE on http://localhost:8080/sse

Pass credentials directly in the env block. Use the absolute path to the venv binary so the config works regardless of which directory the MCP client opens.

{
  "mcpServers": {
    "apec": {
      "command": "/path/to/apec-mcp/.venv/bin/apec-mcp",
      "args": [],
      "env": {
        "MCP_TRANSPORT": "stdio",
        "APEC_EMAIL": "you@example.com",
        "APEC_PASSWORD": "yourpassword",
        "APEC_ID_COMPTE_CADRE": "123456789",
        "APEC_ID_PROFIL_CADRE": "1234567"
      }
    }
  }
}

For Claude Code place this block in ~/.claude.json under mcpServers so all sessions share it.

Docker (SSE mode, optional)

docker build -t apec-mcp:latest .
docker run -d --name apec-mcp-server -p 8081:8080 --env-file .env \
  -e MCP_TRANSPORT=sse apec-mcp:latest

Then point your MCP client at http://localhost:8081/sse.

docker compose may fail to resolve build-cache image digests in some container-in-container environments (OrbStack socktainer, Docker-in-Docker). Plain docker run is more reliable in those setups.


Session Refresh

Session cookies are cached automatically in ~/.apec_session.json and refreshed on 401/403. No manual action is normally needed.

If auto-login is blocked by DataDome:

  1. Open https://www.apec.fr → log in manually

  2. DevTools → Network → any /cms/webservices/* request → Request Headers → Cookie

  3. Add APEC_COOKIES=<value> to .env — this overrides auto-login

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server for job search and application tracking, enabling AI agents to search jobs, get details, manage applications, and find contacts across 128K+ jobs and 1,900+ companies.
    638 npm
    3
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP server that exposes job-search and application-management capabilities to compatible AI clients, enabling discovery of vacancies, drafting of tailored application materials, and coordinated human-approved submissions.
    MIT
  • F
    license
    A
    quality
    B
    maintenance
    MCP server that exposes a resume as callable tools and resources, enabling AI agents to query experience, skills, projects, and contact information via natural language.
    3
    -