feat(oc_mentor): OpenClassrooms RPA + MCP server for Claude Code#8
feat(oc_mentor): OpenClassrooms RPA + MCP server for Claude Code#8tomgrv wants to merge 3 commits into
Conversation
Adds a standalone oc_mentor/ package with 8 MCP tools backed by a Playwright connector to the OpenClassrooms mentor portal: - oc_login / oc_status (auth + health) - oc_list_sessions / oc_get_session / oc_update_session_notes (sessions) - oc_list_projects / oc_get_project / oc_submit_evaluation (evaluations)
There was a problem hiding this comment.
Code Review
This pull request introduces oc_mentor, an OpenClassrooms mentor RPA connector and Model Context Protocol (MCP) server that automates interactions with the mentor portal using Playwright. The review feedback identifies several key improvements and bug fixes: handling <select> elements correctly to avoid Playwright errors when setting competency levels, using a dedicated profile directory for the persistent browser context to prevent workspace pollution, removing a redundant page navigation to optimize speed, and resolving relative deliverable links to absolute URLs.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| if (await levelInput.isVisible().catch(() => false)) { | ||
| await levelInput.fill(String(comp.level)); | ||
| } |
There was a problem hiding this comment.
If the competency level input is a <select> element (which matches the selector [data-competency-id="..."] select), calling fill() on it will throw a Playwright error because fill() is only supported on input, textarea, or contenteditable elements. To support dropdowns, check the element's tag name and use selectOption() for <select> elements.
if (await levelInput.isVisible().catch(() => false)) {
const tagName = await levelInput.evaluate(el => el.tagName.toLowerCase()).catch(() => "");
if (tagName === "select") {
await levelInput.selectOption(String(comp.level));
} else {
await levelInput.fill(String(comp.level));
}
}| existsSync(AUTH_STATE_PATH) && | ||
| Date.now() - statSync(AUTH_STATE_PATH).mtimeMs < SESSION_MAX_AGE_MS; | ||
|
|
||
| _context = await chromium.launchPersistentContext("", { |
There was a problem hiding this comment.
Using an empty string "" as the user data directory for launchPersistentContext will cause Playwright to create the browser profile directories and cache files directly in the current working directory. This pollutes the project workspace. Instead, use a dedicated profile directory inside the existing stateDir (e.g., ~/.oc_mentor/profile).
| _context = await chromium.launchPersistentContext("", { | |
| const profileDir = join(stateDir, "profile"); | |
| _context = await chromium.launchPersistentContext(profileDir, { |
| // OC project detail pages follow /mentor/students/{studentId}/projects/{projectId} | ||
| // We navigate via the project list and find the right one | ||
| await page.goto(`${OC_STUDENTS_URL}`, { waitUntil: "networkidle" }); | ||
|
|
||
| // Try direct URL patterns OC might use | ||
| const directUrl = `https://openclassrooms.com/en/mentor/projects/${projectId}`; | ||
| await page.goto(directUrl, { waitUntil: "networkidle" }); |
There was a problem hiding this comment.
The navigation to OC_STUDENTS_URL on line 89 is redundant because the code immediately navigates to directUrl on line 93. Removing this redundant navigation will improve the tool's execution speed and reduce unnecessary network requests.
| // OC project detail pages follow /mentor/students/{studentId}/projects/{projectId} | |
| // We navigate via the project list and find the right one | |
| await page.goto(`${OC_STUDENTS_URL}`, { waitUntil: "networkidle" }); | |
| // Try direct URL patterns OC might use | |
| const directUrl = `https://openclassrooms.com/en/mentor/projects/${projectId}`; | |
| await page.goto(directUrl, { waitUntil: "networkidle" }); | |
| // Try direct URL patterns OC might use | |
| const directUrl = "https://openclassrooms.com/en/mentor/projects/" + projectId; | |
| await page.goto(directUrl, { waitUntil: "networkidle" }); |
| const deliverableLinks: string[] = []; | ||
| for (const el of linkEls) { | ||
| const href = await el.getAttribute("href").catch(() => null); | ||
| if (href) deliverableLinks.push(href); | ||
| } |
There was a problem hiding this comment.
The collected deliverable links might be relative URLs (e.g., starting with /). Resolving them to absolute URLs using the current page's URL ensures that the MCP client/Claude can directly access them.
| const deliverableLinks: string[] = []; | |
| for (const el of linkEls) { | |
| const href = await el.getAttribute("href").catch(() => null); | |
| if (href) deliverableLinks.push(href); | |
| } | |
| const deliverableLinks: string[] = []; | |
| for (const el of linkEls) { | |
| const href = await el.getAttribute("href").catch(() => null); | |
| if (href) { | |
| try { | |
| deliverableLinks.push(new URL(href, page.url()).href); | |
| } catch { | |
| deliverableLinks.push(href); | |
| } | |
| } | |
| } |
Adds src/http.ts — a Node built-in HTTP server using StreamableHTTPServerTransport with per-session state management and optional Bearer token auth (MCP_AUTH_TOKEN). Updates src/index.ts to switch between stdio (Claude Code) and HTTP (Claude.ai) based on MCP_TRANSPORT=http env var or --http argv flag. Adds start:http / dev:http npm scripts and updates README with Claude.ai Integrations setup instructions.
Summary
oc_mentor/— a standalone TypeScript package containing a Playwright RPA connector to the OpenClassrooms mentor portal + an MCP server exposing it as tools Claude Code can call directlyoc_login,oc_status,oc_list_sessions,oc_get_session,oc_update_session_notes,oc_list_projects,oc_get_project,oc_submit_evaluation~/.oc_mentor/auth-state.json)Intended use
This package is meant to live in its own repository (
alaudida/oc_mentor). Once you create that repo on GitHub:Test plan
cd oc_mentor && npm install && npx playwright install chromiumcp .env.example .envand fill inOC_EMAIL/OC_PASSWORDnpm run build— TypeScript compiles with 0 errorsecho '{"jsonrpc":"2.0","method":"tools/list","id":1}' | node dist/index.js— returns all 8 tools~/.claude/settings.json, restart Claude Code, calloc_status→ returns auth stateoc_list_sessions→ returns real sessions from the OC dashboardGenerated by Claude Code